Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
25 changes: 23 additions & 2 deletions scripts/i18n_check_tokens.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,31 @@ 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, [])
byKey.get(b.key).push(b)
}

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) {
Expand All @@ -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)
Loading