Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
993f983
i18n: string-extraction codemod, literal ratchet and en-XA pseudo-loc…
cursoragent Oct 27, 2026
54ffbe8
i18n codemod: drop a path segment that repeats the package namespace
cursoragent Oct 27, 2026
a9350e5
i18n codemod: lazy descriptors for module data; scan detail copy
cursoragent Oct 27, 2026
12752d8
i18n audit: scope findings, full-viewport screenshots, label-based su…
cursoragent Oct 27, 2026
b4cc700
Remove a file that belongs to the navbar migration
cursoragent Oct 27, 2026
562f425
i18n: migrate navbar and top bar
cursoragent Oct 27, 2026
a165e1a
i18n:check: reviewed translations cannot regress; releases require re…
cursoragent Oct 27, 2026
184a2ae
Literal guard: per-text baseline that may only shrink, stricter scanner
cursoragent Oct 27, 2026
2e08651
Pseudo-locale: dev/test builds only; audit allowlist matches whole wo…
cursoragent Oct 27, 2026
5fa76da
i18n: migrate the viewer toolbar so the literal baseline only shrinks
cursoragent Oct 27, 2026
9c31599
i18n tooling: fast fixture tests; document the release review gate
cursoragent Oct 27, 2026
14facd0
Merge i18n tooling updates from #1167
cursoragent Oct 27, 2026
2ba90c0
i18n extract: never approve a newly filled translation
cursoragent Oct 27, 2026
13b6052
i18n: normalize draft comment order as extract writes it
cursoragent Oct 27, 2026
0134222
Merge main
cursoragent Oct 27, 2026
c1e3a73
Baseline the floor plan panel copy that main changed
cursoragent Oct 27, 2026
8a8eece
Merge i18n tooling updates from #1167
cursoragent Oct 27, 2026
8d8742f
Keep AppHeader server-safe: translated home link moves to a client leaf
cursoragent Oct 27, 2026
abbf51f
i18n codemod: leave app modules without 'use client' for a person
cursoragent Oct 27, 2026
00cf690
Merge i18n tooling updates from #1167
cursoragent Oct 27, 2026
0a80256
Literal guard: resolve rendered constants through lexical scope
cursoragent Oct 27, 2026
72317fd
Merge i18n tooling updates from #1167
cursoragent Oct 27, 2026
e37b746
i18n: releases ship drafts as English; gate on regressions and pack l…
cursoragent Oct 27, 2026
4613317
Merge i18n tooling updates from #1167
cursoragent Oct 27, 2026
fee4995
Literal guard: measure baseline growth repo-wide; scan rendered text
cursoragent Oct 27, 2026
9ae7763
i18n codemod: ICU-exact encoding, entry-module and callback guards, i…
cursoragent Oct 27, 2026
acea493
i18n:check: catch renamed reviewed IDs; compare pushes with github.ev…
cursoragent Oct 27, 2026
444267a
i18n audit: report controls without an accessible name; tests and doc…
cursoragent Oct 27, 2026
6fbe590
Merge i18n tooling updates from #1167
cursoragent Oct 27, 2026
c04270d
Navbar: name Preview and Share at mobile widths
cursoragent Oct 27, 2026
285ce3c
i18n:check: a stale draft after a wording change is a draft, not an e…
cursoragent Oct 27, 2026
c804232
Merge i18n tooling updates from #1167
cursoragent Oct 27, 2026
1032c0f
release-check: compare with the latest @pascal-app/editor release tag
cursoragent Oct 27, 2026
f30dc30
Merge i18n tooling updates from #1167
cursoragent Oct 27, 2026
8776a6d
i18n: renames warn unless declared; codemod and scanner fidelity fixes
cursoragent Oct 27, 2026
76e6098
Merge i18n tooling updates from #1167
cursoragent Oct 27, 2026
53abaae
Literal guard: a trailing i18n-ignore exempts only its own line
cursoragent Oct 27, 2026
70f7702
Merge i18n tooling updates from #1167
cursoragent Oct 27, 2026
96ca6c7
Merge origin/main (squashed #1167) into the navbar branch
cursoragent Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions apps/editor/components/app-home-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client'

import { usePascalI18n } from '@pascal-app/core'
import Link from 'next/link'
import type { ReactNode } from 'react'

/** Server pages render `AppHeader`, so the translated link label lives in this client leaf. */
export function AppHomeLink({ children }: { children: ReactNode }) {
const i18n = usePascalI18n()
return (
<Link
aria-label={i18n._({ id: 'app.appShell.pascalHome', message: 'Pascal home' })}
className="-mx-2 flex items-center rounded-lg px-2 py-1.5 transition-colors hover:bg-accent"
href="/"
>
{children}
</Link>
)
}
10 changes: 3 additions & 7 deletions apps/editor/components/app-shell.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Image from 'next/image'
import Link from 'next/link'
import type { ReactNode } from 'react'
import { cn } from '@/lib/utils'
import { AppHomeLink } from './app-home-link'

/**
* The dark chrome every page outside the editor shares, so the scenes list,
Expand Down Expand Up @@ -49,13 +49,9 @@ export function AppHeader({ children }: { children?: ReactNode }) {
return (
<header className="sticky top-0 z-20 border-border/50 border-b bg-sidebar">
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between gap-4 px-6">
<Link
aria-label="Pascal home"
className="-mx-2 flex items-center rounded-lg px-2 py-1.5 transition-colors hover:bg-accent"
href="/"
>
<AppHomeLink>
<PascalLogo />
</Link>
</AppHomeLink>
<div className="flex items-center gap-2">{children}</div>
</div>
</header>
Expand Down
20 changes: 14 additions & 6 deletions apps/editor/components/editor-navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { usePascalI18n } from '@pascal-app/core'
import { useEditor } from '@pascal-app/editor'
import { Eye, Pencil, Share2 } from 'lucide-react'
import Link from 'next/link'
Expand All @@ -15,6 +16,7 @@ interface EditorNavbarProps {
}

export function EditorNavbar({ sceneId, name, onRename }: EditorNavbarProps) {
const i18n = usePascalI18n()
const [editing, setEditing] = useState(false)
const [shareOpen, setShareOpen] = useState(false)
const shareAnchorRef = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -60,10 +62,10 @@ export function EditorNavbar({ sceneId, name, onRename }: EditorNavbarProps) {
<nav className="flex h-12 shrink-0 items-center gap-2 border-border/30 border-b bg-sidebar px-2 lg:px-3">
<div className="flex min-w-0 items-center gap-1.5 lg:gap-2">
<Link
aria-label="All projects"
aria-label={i18n._({ id: 'app.editorNavbar.allProjects', message: 'All projects' })}
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg transition-colors hover:bg-accent"
href="/"
title="All projects"
title={i18n._({ id: 'app.editorNavbar.allProjects', message: 'All projects' })}
>
<PascalMark />
</Link>
Expand All @@ -73,7 +75,7 @@ export function EditorNavbar({ sceneId, name, onRename }: EditorNavbarProps) {
<div className="min-w-0 max-w-40 lg:max-w-72">
{editing ? (
<input
aria-label="Project name"
aria-label={i18n._({ id: 'app.editorNavbar.projectName', message: 'Project name' })}
className="h-7 w-full rounded-md bg-accent/50 px-2 py-0 font-medium text-foreground text-sm outline-none ring-1 ring-primary/50 focus:ring-primary"
maxLength={200}
onBlur={commit}
Expand All @@ -86,7 +88,7 @@ export function EditorNavbar({ sceneId, name, onRename }: EditorNavbarProps) {
<button
className="group/title flex h-7 max-w-full items-center gap-1.5 rounded-md px-2 text-left transition-colors hover:bg-accent/50"
onClick={startEditing}
title="Rename project"
title={i18n._({ id: 'app.editorNavbar.renameProject', message: 'Rename project' })}
type="button"
>
<span className="truncate font-medium text-sm">{name}</span>
Expand All @@ -99,23 +101,29 @@ export function EditorNavbar({ sceneId, name, onRename }: EditorNavbarProps) {
<div className="flex-1" />

<button
aria-label={i18n._({ id: 'app.editorNavbar.preview', message: 'Preview' })}
className="flex items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 font-medium text-sm transition-colors hover:bg-accent"
onClick={() => useEditor.getState().setPreviewMode(true)}
type="button"
>
<Eye className="h-4 w-4 shrink-0" />
<span className="hidden sm:inline">Preview</span>
<span className="hidden sm:inline">
{i18n._({ id: 'app.editorNavbar.preview', message: 'Preview' })}
</span>
</button>

<div className="relative" ref={shareAnchorRef}>
<button
aria-expanded={shareOpen}
aria-label={i18n._({ id: 'app.editorNavbar.share', message: 'Share' })}
className="flex items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 font-medium text-sm transition-colors hover:bg-accent"
onClick={() => setShareOpen((open) => !open)}
type="button"
>
<Share2 className="h-4 w-4 shrink-0" />
<span className="hidden sm:inline">Share</span>
<span className="hidden sm:inline">
{i18n._({ id: 'app.editorNavbar.share', message: 'Share' })}
</span>
</button>
{shareOpen && (
<SharePanel anchorRef={shareAnchorRef} onClose={closeShare} sceneId={sceneId} />
Expand Down
56 changes: 43 additions & 13 deletions apps/editor/components/share-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { usePascalI18n } from '@pascal-app/core'
import { Check, Copy, ExternalLink } from 'lucide-react'
import { type KeyboardEvent, type RefObject, useEffect, useRef, useState } from 'react'
import { cn } from '@/lib/utils'
Expand Down Expand Up @@ -38,6 +39,7 @@ async function writeShared(sceneId: string, shared: boolean): Promise<boolean> {
}

export function SharePanel({ sceneId, anchorRef, onClose }: SharePanelProps) {
const i18n = usePascalI18n()
// null while the current state is loading.
const [shared, setShared] = useState<boolean | null>(null)
const [saving, setSaving] = useState(false)
Expand All @@ -53,7 +55,12 @@ export function SharePanel({ sceneId, anchorRef, onClose }: SharePanelProps) {
.catch(() => {
if (cancelled) return
setShared(false)
setError('Could not read the sharing state.')
setError(
i18n._({
id: 'app.sharePanel.couldNotReadTheSharingState',
message: 'Could not read the sharing state.',
}),
)
})
return () => {
cancelled = true
Expand Down Expand Up @@ -87,7 +94,17 @@ export function SharePanel({ sceneId, anchorRef, onClose }: SharePanelProps) {
setShared(await writeShared(sceneId, next))
} catch {
setShared(previous)
setError(next ? 'Could not turn on the public link.' : 'Could not turn off the public link.')
setError(
next
? i18n._({
id: 'app.sharePanel.couldNotTurnOnThePublic',
message: 'Could not turn on the public link.',
})
: i18n._({
id: 'app.sharePanel.couldNotTurnOffThePublic',
message: 'Could not turn off the public link.',
}),
)
} finally {
setSaving(false)
}
Expand All @@ -99,7 +116,7 @@ export function SharePanel({ sceneId, anchorRef, onClose }: SharePanelProps) {
return (
// Keystrokes in here (Delete, Cmd+A, tool keys) must not reach the editor's shortcuts.
<div
aria-label="Share project"
aria-label={i18n._({ id: 'app.sharePanel.shareProject', message: 'Share project' })}
className="dark absolute top-full right-0 z-50 mt-2 w-80 rounded-xl border border-border/60 bg-background p-4 text-foreground shadow-2xl [color-scheme:dark]"
onKeyDown={(event: KeyboardEvent) => {
if (event.key !== 'Escape') event.stopPropagation()
Expand All @@ -108,7 +125,7 @@ export function SharePanel({ sceneId, anchorRef, onClose }: SharePanelProps) {
>
<div className="flex items-center justify-between gap-3">
<span className="font-medium text-sm" id="share-public-link-label">
Public link
{i18n._({ id: 'app.sharePanel.publicLink', message: 'Public link' })}
</span>
<Switch
checked={shared === true}
Expand All @@ -118,7 +135,10 @@ export function SharePanel({ sceneId, anchorRef, onClose }: SharePanelProps) {
/>
</div>
<p className="mt-1.5 text-muted-foreground text-xs">
Anyone with the link can view this project. They cannot edit it.
{i18n._({
id: 'app.sharePanel.anyoneWithTheLinkCanView',
message: 'Anyone with the link can view this project. They cannot edit it.',
})}
</p>
{error && <p className="mt-2 text-destructive text-xs">{error}</p>}

Expand All @@ -127,7 +147,7 @@ export function SharePanel({ sceneId, anchorRef, onClose }: SharePanelProps) {
<div>
<div className="flex items-center justify-between gap-3">
<span className="font-medium text-sm" id="share-hide-controls-label">
Hide controls
{i18n._({ id: 'app.sharePanel.hideControls', message: 'Hide controls' })}
</span>
<Switch
checked={hideControls}
Expand All @@ -136,14 +156,19 @@ export function SharePanel({ sceneId, anchorRef, onClose }: SharePanelProps) {
/>
</div>
<p className="mt-1.5 text-muted-foreground text-xs">
Only the 3D view, no header or toolbar.
{i18n._({
id: 'app.sharePanel.onlyThe3dViewNoHeader',
message: 'Only the 3D view, no header or toolbar.',
})}
</p>
</div>
<div>
<span className="mb-1.5 block text-muted-foreground text-xs">Link</span>
<span className="mb-1.5 block text-muted-foreground text-xs">
{i18n._({ id: 'app.sharePanel.link', message: 'Link' })}
</span>
<div className="flex items-center gap-1.5">
<input
aria-label="Embed link"
aria-label={i18n._({ id: 'app.sharePanel.embedLink', message: 'Embed link' })}
className={FIELD}
onFocus={(event) => event.currentTarget.select()}
readOnly
Expand All @@ -158,16 +183,18 @@ export function SharePanel({ sceneId, anchorRef, onClose }: SharePanelProps) {
target="_blank"
>
<ExternalLink className="h-3 w-3" />
Open
{i18n._({ id: 'app.sharePanel.open', message: 'Open' })}
</a>
</div>
<div>
<div className="mb-1.5 flex items-center justify-between">
<span className="text-muted-foreground text-xs">Embed code</span>
<span className="text-muted-foreground text-xs">
{i18n._({ id: 'app.sharePanel.embedCode', message: 'Embed code' })}
</span>
<CopyButton text={snippet} />
</div>
<textarea
aria-label="Embed code"
aria-label={i18n._({ id: 'app.sharePanel.embedCode', message: 'Embed code' })}
className={cn(FIELD, 'h-20 resize-none leading-relaxed')}
onFocus={(event) => event.currentTarget.select()}
readOnly
Expand Down Expand Up @@ -215,6 +242,7 @@ function Switch({
}

function CopyButton({ text }: { text: string }) {
const i18n = usePascalI18n()
const [copied, setCopied] = useState(false)
const timer = useRef<ReturnType<typeof setTimeout> | null>(null)

Expand All @@ -240,7 +268,9 @@ function CopyButton({ text }: { text: string }) {
return (
<button className={SMALL_BUTTON} onClick={copy} type="button">
{copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
{copied ? 'Copied' : 'Copy'}
{copied
? i18n._({ id: 'app.sharePanel.copied', message: 'Copied' })
: i18n._({ id: 'app.sharePanel.copy', message: 'Copy' })}
</button>
)
}
49 changes: 0 additions & 49 deletions i18n/literal-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@
"Set up · Pascal",
"This editor is reachable but not protected yet"
],
"apps/editor/components/app-shell.tsx": [
"Pascal home"
],
"apps/editor/components/build-tab.tsx": [
"Add Gate",
"Add Open Passage",
Expand Down Expand Up @@ -123,14 +120,6 @@
"Failed to create project ({status})",
"New project"
],
"apps/editor/components/editor-navbar.tsx": [
"All projects",
"All projects",
"Preview",
"Project name",
"Rename project",
"Share"
],
"apps/editor/components/embed-viewer.tsx": [
"Made with Pascal",
"This project could not be shown in 3D here. Try a browser with hardware acceleration turned on."
Expand Down Expand Up @@ -180,23 +169,6 @@
"Settings",
"Your changes haven't been saved. Reload to pick up the latest version."
],
"apps/editor/components/share-panel.tsx": [
"Anyone with the link can view this project. They cannot edit it.",
"Copied",
"Copy",
"Could not read the sharing state.",
"Could not turn off the public link.",
"Could not turn on the public link.",
"Embed code",
"Embed code",
"Embed link",
"Hide controls",
"Link",
"Only the 3D view, no header or toolbar.",
"Open",
"Public link",
"Share project"
],
"packages/editor/src/components/editor-2d/floorplan-measurement-tool-layer.tsx": [
"ALIGN ",
"Create",
Expand Down Expand Up @@ -428,27 +400,6 @@
"uninstalled",
"{label} details"
],
"packages/editor/src/components/editor/views/view-bar.tsx": [
"Auto",
"Browse plugins",
"Close split (\\)",
"Close {label}",
"Ground",
"Keep the ground solid",
"More views",
"Pane {?} views",
"Pin to the view bar",
"Pin {label}",
"Solid",
"Split",
"Split (\\)",
"Swap panes",
"Swap panes",
"Unpin from the view bar",
"Unpin {label}",
"Veil the ground over a level below grade",
"Views"
],
"packages/editor/src/components/point-ask/bubble.tsx": [
"Ask Pascal",
"Ask Pascal",
Expand Down
31 changes: 31 additions & 0 deletions i18n/pseudo-surfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,35 @@ export const surfaces: PseudoSurface[] = [
{ name: 'home', path: '/', data: ['Project #1'] },
{ name: 'settings', path: '/settings' },
{ name: 'editor', path: editorPath, steps: openEditor, data: ['Project #1'] },
{ name: 'navbar', path: editorPath, steps: openEditor, scope: 'nav', data: ['Project #1'] },
{
name: 'navbar-mobile',
path: editorPath,
steps: openEditor,
scope: 'nav',
viewport: { width: 390, height: 844 },
data: ['Project #1'],
},
{
name: 'share-panel',
path: editorPath,
steps: async (page) => {
await openEditor(page)
await page.locator('nav button[aria-expanded]').click()
await page.waitForTimeout(800)
},
scope: 'nav [role="dialog"]',
},
{
name: 'display-menu',
path: editorPath,
steps: (page) => openInEditor(page, 'Display settings'),
scope: '[role="menu"]',
},
{
name: 'more-views',
path: editorPath,
steps: (page) => openInEditor(page, 'More views'),
scope: '[role="menu"]',
},
]
Loading
Loading