Skip to content

[BUG]: 19 locale dictionaries carry 116 entries for key families English no longer defines #56610

Open

Description

@dat-rd

19 of the 62 app locale dictionaries still define key families that packages/app/src/runtime/i18n/en.ts no longer has:

  • session.question.pending
  • session.background.runningCount
  • session.background.shell
  • session.background.subagent

That leaves 116 dead entries across them, every one a CLDR plural variant (few, many, zero or two) of those families.

None is reachable. A lookup only asks for keys the English dictionary defines, and those families were removed from en.ts, so no code path selects the strings. Nothing outside runtime/i18n references them. packages/ui has none.

This blocks the "no locale defines a key English does not" assertion in #50835: the locales define families English does not have, so that check cannot pass until they are removed. #50772 asks for the same class of test.

Separately, the same dictionaries still carry translations for strings equal to their English source. Across every locale that is 44 values in the app package, so it is not worth a sweep, but the checks in packages/app/AGENTS.md under Localization ask for it before a locale is considered ready.

Activity

  1. opencode-agent commented on Oct 18, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report, I can confirm this.

    The script below loads packages/app/src/runtime/i18n/en.ts and every locale dictionary, then lists the locale keys whose family English doesn't define. Extra plural forms (zero/two/few/many) are still allowed when English defines that family. On v2.0.32 and on the latest dev (4745089), it finds 19 locales with 116 extra entries, all plural forms of session.question.pending, session.background.runningCount, session.background.shell and session.background.subagent. pluralForm() in language.tsx only looks up families that English defines with both .one and .other, so these strings can never be shown.

    I did not check the separate point about the 44 values that are identical to English.

    Related:

    Reproduction script (check-locales.ts)
    // Reproduction for anomalyco/opencode#56610.
    // Run from an OpenCode checkout: bun /workspace/repro/check-locales.ts [repoDir]
    // Lists locale keys (ignoring CLDR variants of plural families en defines) in packages/app/src/runtime/i18n/*.ts whose key (or plural base) English does not define.
    import { readdirSync } from "node:fs"
    import { join } from "node:path"
    
    const repo = process.argv[2] ?? "/workspace/repo"
    const dir = join(repo, "packages/app/src/runtime/i18n")
    const plural = /\.(zero|one|two|few|many|other)$/
    
    const en = (await import(join(dir, "en.ts"))).dict as Record<string, string>
    const enKeys = new Set(Object.keys(en))
    const enBases = new Set([...enKeys].filter((k) => plural.test(k)).map((k) => k.replace(plural, "")))
    
    let total = 0
    const files: string[] = []
    const families = new Set<string>()
    for (const f of readdirSync(dir).sort()) {
      if (!f.endsWith(".ts") || f.endsWith(".test.ts") || f === "en.ts" || f === "desktop-native.ts") continue
      const mod = await import(join(dir, f))
      const dict = mod.dict as Record<string, string> | undefined
      if (!dict) continue
      // Keys English lacks, ignoring legitimate CLDR variants of plural families English defines.
      const extra = Object.keys(dict).filter((k) => !enKeys.has(k) && !enBases.has(k.replace(plural, "")))
      if (!extra.length) continue
      files.push(f)
      total += extra.length
      for (const k of extra) {
        const base = k.replace(plural, "")
        families.add(base)
      }
      console.log(`${f}: ${extra.length} -> ${extra.join(", ")}`)
    }
    console.log(`\nlocales with keys English lacks: ${files.length}`)
    console.log(`total extra entries: ${total}`)
    console.log(`families:\n  ${[...families].sort().join("\n  ")}`)
    process.exit(total ? 1 : 0)

    Run it: bun check-locales.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions