Skip to content

Add i18n locale support with 81 translations#421

Open
cat5inthecradle wants to merge 4 commits intomainfrom
claude/priceless-dubinsky
Open

Add i18n locale support with 81 translations#421
cat5inthecradle wants to merge 4 commits intomainfrom
claude/priceless-dubinsky

Conversation

@cat5inthecradle
Copy link
Copy Markdown

@cat5inthecradle cat5inthecradle commented Mar 10, 2026

This is a Claude written PR. I was curious what it would look like to pull in translations. - Darin

Summary

  • Copies 81 non-English translation files from code-dot-org/code-dot-org apps/i18n/fish/ into i18n/locales/
  • Adds runtime locale selection via ?locale=xx_xx query parameter for the standalone demo
  • Extends the initAll() API to accept raw translation JSON alongside the existing pre-compiled function override path
  • Adds bin/syncTranslations.js script for keeping translations in sync with the monorepo

Motivation

We are interested in hosting this activity in an iframe and needs localization support. The translations already exist in the main code-dot-org monorepo but weren't accessible to standalone consumers of this repo.

Technical decisions

Runtime fetch vs. build-time bundling for locale files

Locale JSON files are fetched at runtime (fetch('locales/es_es.json')) rather than bundled into the main JS bundle. Bundling all 81 locales (~587KB of JSON) would bloat oceans.js for every user when only one locale is needed per session. Runtime fetch means a single small (7-20KB) request for just the locale needed, and English users pay nothing.

Tradeoff: There's a brief async gap before translations load. In practice the JSON files are tiny and same-origin so this is negligible.

Raw strings vs. pre-compiled function detection in initI18n

The original initI18n assumed overrides were pre-compiled MessageFormat functions (as the main code-dot-org host app provides). A type check was added — typeof values[0] === 'string' — to auto-compile raw JSON strings when detected.

This preserves backward compatibility: the host app integration path (passing pre-compiled functions) still works unchanged, while the standalone demo and new iframe embedders can pass raw JSON translation objects directly.

Locale code extraction: es_eses

Translation files use xx_xx naming (e.g. es_es, pt_br) but MessageFormat v2.3.0's constructor expects a BCP 47 language code like es to select pluralization rules. The language portion is split from the locale and passed to MessageFormat.

Regional variants (pt_br vs pt_pt) share pluralization rules, so this is correct — the actual translated strings remain distinct per file.

Locale files as CopyPlugin static assets

Locale files are added to the existing CopyPlugin config rather than processed as webpack modules. They need to be individually addressable by URL at runtime for the fetch. CopyPlugin copies them verbatim to dist/locales/, which is exactly what's needed — webpack module processing would either bundle them all or create content-hashed chunk filenames that runtime fetch couldn't predict.

?locale= query parameter

Follows the existing convention of the standalone demo page, which already uses query parameters for configuration (?mode=, ?guides=, ?tts=). This is the standard approach for passing config into iframe-embedded apps — stateless, visible, and controlled entirely by the embedder via the src attribute.

Graceful fallback on fetch failure

If a locale file doesn't exist or fetch fails, the app logs a warning and initializes in English. The ?locale= param comes from URL input that could be a typo or unsupported locale — crashing the activity over it would be a poor experience.

Translation coverage

81 locale files are included. Most are at 97%+ coverage (104/107 keys). The 3 universally missing keys are recently added strings:

  • training-generic-please-continue
  • switchToMatchingItems
  • switchToNonMatchingItems

EU language coverage (24 official languages)

Language Locale Keys Coverage Status
Bulgarian bg_bg 104/107 97%
Croatian hr_hr 104/107 97%
Czech cs_cz 104/107 97%
Danish da_dk 10/107 9% ⚠️ Stub
Dutch nl_nl 104/107 97%
English base 107/107 100%
Estonian et_ee 7/107 7% ⚠️ Stub
Finnish fi_fi 105/107 98%
French fr_fr 104/107 97%
German de_de 104/107 97%
Greek el_gr 104/107 97%
Hungarian hu_hu 104/107 97%
Irish ga_ie 104/107 97%
Italian it_it 104/107 97%
Latvian lv_lv 7/107 7% ⚠️ Stub
Lithuanian lt_lt 7/107 7% ⚠️ Stub
Maltese mt_mt 1/107 1% ⚠️ Stub
Polish pl_pl 104/107 97%
Portuguese pt_pt 104/107 97%
Romanian ro_ro 104/107 97%
Slovak sk_sk 104/107 97%
Slovenian sl_si 94/107 88% ⚠️ Partial
Spanish es_es 105/107 98%
Swedish sv_se 104/107 97%

18/24 EU languages are at 97%+. 5 are essentially untranslated stubs (Danish, Estonian, Latvian, Lithuanian, Maltese) and Slovenian is partial at 88%. These stub files will fall back to English for missing keys, so they won't break — but they won't deliver a localized experience either. Translation coverage reflects what exists in the upstream monorepo today.

Usage

  • Default (English): localhost:8080
  • With locale: localhost:8080?locale=es_es
  • In an iframe: <iframe src="https://host/index.html?locale=fr_fr">
  • Via the API: initAll({i18n: translationJSON, locale: 'fr'})
  • Re-sync translations: yarn sync-i18n (GitHub) or yarn sync-i18n:local ~/code/code-dot-org

Test plan

  • yarn dev — app loads at localhost:8080 in English (no regression)
  • localhost:8080?locale=es_es — Spanish strings appear
  • localhost:8080?locale=fr_fr — French strings appear
  • localhost:8080?locale=bogus — falls back to English with console warning
  • No ?locale param — English works as before (backward compat)
  • yarn build — production build succeeds, locale files appear in dist/locales/

🤖 Generated with Claude Code

Load non-English translation files from code-dot-org/code-dot-org
apps/i18n/fish/ into this repo and wire up runtime locale selection
via ?locale=xx_xx query parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cat5inthecradle cat5inthecradle marked this pull request as draft March 10, 2026 22:25
Add curly braces to if-statement in index.jsx to satisfy the eslint
curly rule. Add a "Publishing and hosting the activity" section to
the README explaining how to build, host, and embed the activity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cat5inthecradle
Copy link
Copy Markdown
Author

Added a note to the PR description about coverage of official EU languages.

Add the tts query parameter to the embedding documentation so
iframe consumers know how to load translated versions of the activity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants