A user asked for Source Cooperative in Japanese. This issue tracks making the app multilingual: Japanese first, then other major languages.
Approach
The app uses next-intl. Each language is one catalog file, src/i18n/messages/{locale}.json, and English is the source of truth.
The locale isn't part of the URL, because accounts own the top level of the path (/{account}/{product}) and a /ja prefix would collide with account ids. Each request resolves it in this order:
- A
NEXT_LOCALE cookie, set by the language switcher in the footer.
- Otherwise, the browser's
Accept-Language header.
- Otherwise, English.
So a visitor whose browser is set to a supported language gets it automatically on their first visit.
In scope:
- All of the app's own UI text, including page metadata and the messages server actions return to forms.
- Dates and numbers, formatted in the reader's locale.
Out of scope:
- User content: product titles, descriptions, READMEs and account names.
- HTTP API responses, which stay English.
- Log messages.
Work
Known gaps and costs
- Errors shared with the API:
serviceAccountGrantProblem() in src/lib/accounts/service-accounts.ts is used by both the server actions and the HTTP API, so its two service-account grant errors stay English. Translating them for the UI only would mean returning a code and mapping it to text in the actions.
- Byte units:
formatBytes in src/lib/format.ts always uses English units (KB, MB).
- Static rendering: resolving the locale reads cookies and headers, so every route renders dynamically, including the marketing homepage and
/products, which were static before.
- Page weight:
NextIntlClientProvider sends the whole catalog for the active locale with each page (about 21 KB gzipped for Japanese). If that becomes noticeable, pass each client subtree only the namespaces it uses.
A user asked for Source Cooperative in Japanese. This issue tracks making the app multilingual: Japanese first, then other major languages.
Approach
The app uses next-intl. Each language is one catalog file,
src/i18n/messages/{locale}.json, and English is the source of truth.The locale isn't part of the URL, because accounts own the top level of the path (
/{account}/{product}) and a/japrefix would collide with account ids. Each request resolves it in this order:NEXT_LOCALEcookie, set by the language switcher in the footer.Accept-Languageheader.So a visitor whose browser is set to a supported language gets it automatically on their first visit.
In scope:
Out of scope:
Work
ja.jsonbefore announcing Japanese support?lang=jaon any URL sets the cookie, then redirects to the same URL without the parameter (feat(i18n): translate the app into Japanese #649)docs/using-source/src/i18n/locales.ts. Candidates: Spanish, Brazilian Portuguese, French, German, Simplified Chinese.en.json.Known gaps and costs
serviceAccountGrantProblem()insrc/lib/accounts/service-accounts.tsis used by both the server actions and the HTTP API, so its two service-account grant errors stay English. Translating them for the UI only would mean returning a code and mapping it to text in the actions.formatBytesinsrc/lib/format.tsalways uses English units (KB, MB)./products, which were static before.NextIntlClientProvidersends the whole catalog for the active locale with each page (about 21 KB gzipped for Japanese). If that becomes noticeable, pass each client subtree only the namespaces it uses.