i18n: add language toggle, neutrality policy, and codeowner gate (#238)

PR #226 landed the i18n infrastructure and Chinese (zh-CN) translations.
This follow-up adds the safeguards that make accepting community
translations sustainable without exposing the project to subtle
state-aligned framing in future translation PRs.

Changes:

  frontend/src/i18n/index.tsx (renamed from .ts)
    - Add LOCALES registry: a single source of truth for available
      languages and their NATIVE display names ("English", "中文 (简体)").
      Adding a new language is now a one-entry change here plus a
      JSON file.
    - Add isLocale() guard so an unknown value in localStorage falls
      through to navigator.language detection instead of corrupting
      state.
    - File renamed to .tsx because it contains JSX. Next.js tolerated
      JSX in .ts but Vite/Oxc (used by vitest) does not.

  frontend/src/components/SettingsPanel.tsx
    Add a UI language picker to the Settings header — a small <select>
    populated from LOCALES. Users no longer need the dev console to
    switch languages. Locale change remains 100% client-side
    (localStorage), no network call, no telemetry.

  CONTRIBUTING.md (new)
    Documents the translation-neutrality requirement that applies
    symmetrically to all source countries:
      - Translations must be technically faithful to the English source.
      - Substitutions aligned with state propaganda from ANY country
        (PRC, Russia, US, EU, etc.) will be rejected.
      - The test is: "would a translator working strictly from the
        English source produce this rendering?"
    Also explains how translation PRs are reviewed and how to add
    a new language.

  .github/CODEOWNERS (new)
    Auto-requests maintainer review on:
      - /frontend/src/i18n/  (translation safety)
      - /backend/auth.py, /backend/routers/wormhole.py,
        /backend/services/mesh/, /backend/services/fetchers/
        (the same paths recent security audits flagged as sensitive)
      - /.github/workflows/, /.gitlab-ci.yml, /docker-compose*.yml,
        /helm/  (build/deploy)
      - /CONTRIBUTING.md, /.github/CODEOWNERS  (policy itself)

  frontend/src/__tests__/i18n/i18nProvider.test.tsx (new, 8 tests)
    Locks in the i18n contract:
      - LOCALES has both en and zh-CN with non-empty native labels
      - Default English when navigator is English
      - Auto-detect zh-CN when navigator language starts with "zh"
      - localStorage preference overrides auto-detect
      - setLocale persists to localStorage
      - Unknown stored locale falls back to auto-detect
      - Renders a real zh-CN translation (catches large-scale
        translation removal in future PRs)
      - Missing key falls back to the key itself

  Note: i18n/index.tsx, the language toggle UI, the translation
  policy, and the test suite together form a defense-in-depth setup.
  The structural safety guarantee (no network calls, static JSON
  bundled at build) is intact; this PR makes the social contract
  around translations explicit and enforceable via branch
  protection on CODEOWNERS-marked paths.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Shadowbroker
2026-05-19 01:48:24 -06:00
committed by GitHub
parent 9ae0b189ba
commit e3297e9bc0
6 changed files with 393 additions and 81 deletions
+32
View File
@@ -0,0 +1,32 @@
# CODEOWNERS — assigns required reviewers for sensitive paths.
# Format: <path glob> <user-or-team> [<user-or-team> ...]
# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
#
# Owners listed here are auto-requested for review when matching files
# change in a PR. If branch protection requires CODEOWNERS approval, the
# PR cannot be merged until an owner approves.
# ── Internationalization / translations ──
# Translation contributions are held to a stricter neutrality standard
# than most code changes — see CONTRIBUTING.md "Translation contributions".
# The i18n layer itself (no network calls, no telemetry, static JSON
# bundled at build) is the structural guarantee that makes this safe;
# changes to it need owner review.
/frontend/src/i18n/ @BigBodyCobain
# ── Security-sensitive code paths ──
/backend/auth.py @BigBodyCobain
/backend/routers/wormhole.py @BigBodyCobain
/backend/services/mesh/ @BigBodyCobain
/backend/services/fetchers/ @BigBodyCobain
# ── CI / build / deploy infra ──
/.github/workflows/ @BigBodyCobain
/.gitlab-ci.yml @BigBodyCobain
/docker-compose.yml @BigBodyCobain
/docker-compose.gitlab.yml @BigBodyCobain
/helm/ @BigBodyCobain
# ── This file and policy docs ──
/.github/CODEOWNERS @BigBodyCobain
/CONTRIBUTING.md @BigBodyCobain