diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b4fa1cd..33f20a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,3 +49,7 @@ cargo test -p server --test scenarios -- --ignored --test-threads=1 - Commit messages use conventional style, lowercase single line: `feat(delivery): …` / `fix(web): …`. - One PR does one thing; include tests; UI changes attach screenshots. - PR description should state the motivation and how it was verified. +- Long-lived branches must not be squash-merged and kept open: a squash merge rewrites the + history on the target, so the surviving branch diverges and its next PR conflicts with + content that is already merged. After squash-merging a long-lived branch, reset/delete it + and open follow-up PRs from the new base (see PR #141 for the failure mode). diff --git a/scripts/i18n_check_tokens.mjs b/scripts/i18n_check_tokens.mjs index 115c899..5fee45c 100644 --- a/scripts/i18n_check_tokens.mjs +++ b/scripts/i18n_check_tokens.mjs @@ -27,6 +27,23 @@ for (const [key, v] of Object.entries(base)) { } } +// Product/brand names must survive translation verbatim in every locale. They +// are proper nouns, not prose: a machine-translation pass must not drop or +// rewrite them (e.g. "Codex" once got eaten down to "n" in several locales). +const BRANDS = ['Claude Code', 'OpenCode', 'CodeBuddy', 'DeepCode', 'Codex'] + +const badBrand = [] +for (const [key, v] of Object.entries(base)) { + const src = (v.en && v.en.trim() ? v.en : v.zh) || '' + for (const brand of BRANDS) { + if (!src.includes(brand)) continue + for (const l of LANGS) { + const text = String(locales[l][key] || '') + if (!text.includes(brand)) badBrand.push({ key, lang: l, brand, text }) + } + } +} + const byKey = new Map() for (const b of bad) { if (!byKey.has(b.key)) byKey.set(b.key, []) @@ -34,7 +51,7 @@ for (const b of bad) { } if (process.argv.includes('--json')) { - console.log(JSON.stringify([...byKey.keys()], null, 2)) + console.log(JSON.stringify([...new Set([...byKey.keys(), ...badBrand.map((b) => b.key)])], null, 2)) } else { console.log('keys with token mismatch:', byKey.size, '/ occurrences:', bad.length) for (const [key, list] of byKey) { @@ -43,5 +60,9 @@ if (process.argv.includes('--json')) { console.log(' langs:', list.map((x) => x.lang).join(' ')) console.log(' e.g.', list[0].lang, '=', JSON.stringify(list[0].text)) } + console.log('\nbrand name drift:', badBrand.length) + for (const b of badBrand) { + console.log(` ${b.key} ${b.lang}: missing ${JSON.stringify(b.brand)} in ${JSON.stringify(b.text)}`) + } } -process.exit(byKey.size ? 1 : 0) +process.exit(byKey.size || badBrand.length ? 1 : 0)