Phase 19: the copy stops being hardcoded Mandarin
Every `中文 · English` string moves out of ~29 components into web/src/i18n: one Pack type, a verbatim zh pack, and two ways to read it — usePack() for components, pack() for the modules that build a line when something happens rather than when something renders. Anything with a value in it is a function on the pack rather than a template at the call site, English pluralisation included: word order isn't universal, and a pack author has to be able to move the number. The roster constants (tones, rewrite styles, export formats, companions) keep only value + emoji, so a label can't drift from its key. On the server, internal/llm/lang.go replaces "Simplified Chinese" in the three prompts that actually name her language. pt-PT is spelled "European Portuguese (pt-PT, never Brazilian Portuguese)" in the prompt itself, and each Lang carries her word for "why" so the tutor prompt still recognises the question when she asks it her way. pair_lang reaches the model through the row-scoped query each handler already ran — the one that proves she owns the document — rather than a second lookup that could disagree with it. Also records Phase 18's deploy: migration 0011 rehearsed against a copy of the live VPS database, then applied for real.
This commit is contained in:
+7
-4
@@ -18,6 +18,7 @@ import { useSession } from './hooks/useSession'
|
||||
import { takeDraft } from './lib/drafts'
|
||||
import { useVersionWatch } from './hooks/useVersionWatch'
|
||||
import { PetalFall } from './effects/PetalFall'
|
||||
import { usePack } from './i18n'
|
||||
import { useNightMode } from './hooks/useNightMode'
|
||||
import { playSuggestionSound } from './audio/sounds'
|
||||
|
||||
@@ -30,6 +31,7 @@ export default function App() {
|
||||
// Who's writing, and whether the server still recognises them. `signedOut`
|
||||
// flips the moment any call comes back 401.
|
||||
const { me, signedOut } = useSession()
|
||||
const t = usePack()
|
||||
// A real account to sign out of, as opposed to the hardcoded local user a
|
||||
// build without auth configured runs as.
|
||||
const account = me && me.id !== 'local' ? { name: me.display_name || me.email } : null
|
||||
@@ -239,7 +241,8 @@ export default function App() {
|
||||
setDocText('')
|
||||
}, [saveNow, isBlankDraft])
|
||||
|
||||
// Duplicate a document: copy its body/tone into a fresh doc titled "… (副本)",
|
||||
// Duplicate a document: copy its body/tone into a fresh doc under the pack's
|
||||
// "copy" title,
|
||||
// then open the copy. Tags aren't carried over (a fresh start for the copy).
|
||||
const handleDuplicate = useCallback(
|
||||
async (id: string) => {
|
||||
@@ -247,7 +250,7 @@ export default function App() {
|
||||
await saveNow() // flush in case we're duplicating the open doc
|
||||
const src = await api.getDoc(id)
|
||||
const fresh = await api.createDoc()
|
||||
const dupTitle = `${src.title?.trim() || 'Untitled'} (副本)`
|
||||
const dupTitle = t.app.duplicateTitle(src.title?.trim() || 'Untitled')
|
||||
const updated = await api.updateDoc(fresh.id, {
|
||||
title: dupTitle,
|
||||
content: src.content,
|
||||
@@ -451,7 +454,7 @@ export default function App() {
|
||||
}}
|
||||
>
|
||||
<span aria-hidden>🌷</span>
|
||||
<span>词汇花园</span>
|
||||
<span>{t.app.garden}</span>
|
||||
<span style={{ color: 'var(--color-muted)' }}>· Garden</span>
|
||||
</button>
|
||||
</header>
|
||||
@@ -514,7 +517,7 @@ export default function App() {
|
||||
}}
|
||||
>
|
||||
<span aria-hidden>🕘</span>
|
||||
<span>历史</span>
|
||||
<span>{t.app.history}</span>
|
||||
<span style={{ color: 'var(--color-muted)' }}>· History</span>
|
||||
</button>
|
||||
<div className="petal-no-print">
|
||||
|
||||
Reference in New Issue
Block a user