Skip to content

Commit fdd923e

Browse files
committed
Add intl docs
1 parent 2fe2e28 commit fdd923e

File tree

3 files changed

+484
-0
lines changed

3 files changed

+484
-0
lines changed

docs/intl/add-new-language.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Add a new language
2+
3+
First add the new language in Weblate. This will create empty translation files
4+
for the new language in the `weblate` branch.
5+
6+
The following files will be automatically generated:
7+
8+
- `vault-core/src/intl/locales/LOCALE/extracted.json`
9+
(`LOCALE` will be in BCP 47 style, e.g. `zh-Hans`)
10+
- `vault-web/src/features/intl/locales/LOCALE/extracted.json`
11+
(`LOCALE` will be in BCP 47 style, e.g. `zh-Hans`)
12+
- `vault-ios/res/values-LOCALE/strings.xml`
13+
(`LOCALE` will be in BCP 47 style, e.g. `zh-Hans`)
14+
- `vault-android/app/src/main/res/values-LOCALE/strings.xml`
15+
(`LOCALE` will be in Android style, e.g. `zh-rCN`)
16+
17+
Fetch latest remote refs and create an integration branch from
18+
`origin/weblate`:
19+
20+
```sh
21+
git fetch origin
22+
git checkout -b add-language-LOCALE origin/weblate
23+
git rebase origin/main
24+
```
25+
26+
1. Add the new locale to `vault-core/src/intl/locales/locales.json`
27+
28+
2. Add the new locale to `vault-web/src/features/intl/locales/locales.json`
29+
(used for landing page where the WASM bundle is not loaded) and
30+
`vault-web/src/features/intl/getDateFnsLocale.ts`
31+
32+
3. Add the new locale to iOS Xcode project:
33+
- in Xcode Project Navigator, click the `Vault` project
34+
- select the `Vault` project (not `Vault` target), open `Info` tab, add the
35+
new locale to `Localizations`
36+
37+
4. Add the new locale to Android:
38+
- in `vault-android/app/build.gradle.kts` add the new locale to
39+
`androidResources.localeFilters`
40+
41+
5. Regenerate ICU data:
42+
43+
```sh
44+
make intl-generate-icu-data
45+
```
46+
47+
6. Compile/import the locales:
48+
49+
```sh
50+
make intl-compile
51+
```

docs/intl/index.md

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# Internationalization
2+
3+
This page documents how intl is implemented across Vault components and where
4+
translation files flow through the system.
5+
6+
Related docs:
7+
8+
- `docs/intl/setup-weblate.md`
9+
- `docs/intl/add-new-language.md`
10+
11+
## System overview
12+
13+
Vault has two intl stacks:
14+
15+
- Shared Rust/web stack: `vault-intl` + `vault-core/src/intl` +
16+
`vault-web/src/features/intl`
17+
- Native mobile string stacks: `vault-ios` (`.xcstrings`) and
18+
`vault-android` (`strings.xml`)
19+
20+
Core and Web use ICU MessageFormat via FormatJS AST catalogs. iOS and Android
21+
render native localized strings in UI, while still using core locale state for
22+
locale selection/subscriptions.
23+
24+
## Key directories
25+
26+
- `vault-intl`
27+
Rust intl runtime helpers (formatting, plural rules, language negotiation,
28+
baked ICU data provider)
29+
- `vault-core/src/intl`
30+
Core intl state/service/selectors and bundled locale catalogs
31+
- `vault-web/src/features/intl`
32+
React providers and locale/message loading for authenticated and landing page
33+
flows
34+
- `vault-ios/VaultCommon/Features/Intl`
35+
iOS language picker UI bound to `MobileVault` intl subscriptions
36+
- `vault-android/app/src/main/java/net/koofr/vault/features/intl`
37+
Android locale sync helper + language picker UI
38+
39+
## Core and `vault-intl`
40+
41+
Core authoring uses:
42+
43+
```rust
44+
format_message!(formatter, "message.id", "Description", "Default message", args)
45+
```
46+
47+
At runtime:
48+
49+
- `IntlState` holds locales, default locale, current locale, ownership mode
50+
- `IntlService` initializes default/current formatters and persists locale under
51+
`vaultIntlCurrentLocale` when ownership is core-managed
52+
- Locale matching uses `vault_intl::negotiate_language(...)`
53+
- `FallbackFormatter` always falls back to default locale catalog
54+
55+
Catalog loading:
56+
57+
- `vault-core/build.rs` reads `vault-core/src/intl/locales/locales.json`
58+
- For each locale, it reads `compiled.json`, validates formatter construction,
59+
minifies JSON, and generates `OUT_DIR/.../locales.min.json`
60+
- `vault-core/src/intl/locales.rs` includes generated locales at compile time
61+
62+
## Web (`vault-web/src/features/intl`)
63+
64+
Two intl modes exist:
65+
66+
- Authenticated app (`WebVaultIntlProvider`)
67+
locale and locale list come from wasm/core
68+
(`intlCurrentLocaleSubscribe`, `intlLocalesSubscribe`)
69+
- Unauthenticated landing flow (`LocalStorageIntlProvider`)
70+
locale is stored in browser `localStorage` (`vaultIntlCurrentLocale`) and
71+
initialized from `navigator.languages` using
72+
`@formatjs/intl-localematcher`
73+
74+
Message loading:
75+
76+
- `getMessages.ts` imports all `./locales/*/compiled.json` via Vite glob
77+
- `react-intl` `IntlProvider` renders those messages
78+
- `DateFnsLocaleProvider` maps core locale to date-fns locale
79+
80+
## iOS (`vault-ios`)
81+
82+
iOS UI strings are authored in `vault-ios/VaultCommon/Resources/Localizable.xcstrings`.
83+
84+
Because Weblate does not support `.xcstrings`, iOS uses a conversion bridge:
85+
86+
- export step: `.xcstrings` -> `vault-ios/res/values*/strings.xml`
87+
- import step: `vault-ios/res/values*/strings.xml` -> `.xcstrings`
88+
- tool: `vault-ios/scripts/xcstrings-convert.py`
89+
90+
Runtime locale behavior:
91+
92+
- iOS initializes `MobileVault` with `IntlOwnership.core(preferredLocales: Locale.preferredLanguages)`
93+
- Current core locale is subscribed and injected into SwiftUI via
94+
`.environment(\.locale, Locale(identifier: currentLocale.locale))`
95+
96+
## Android (`vault-android`)
97+
98+
Android UI strings are native resources:
99+
100+
- base (en): `vault-android/app/src/main/res/values/strings.xml`
101+
- translations: `vault-android/app/src/main/res/values-*/strings.xml`
102+
103+
Runtime locale behavior:
104+
105+
- Android initializes `MobileVault` with `IntlOwnership.External`
106+
- App locale is managed with `AppCompatDelegate.setApplicationLocales(...)`
107+
- `IntlHelper.updateMobileVaultIntlCurrentLocale(...)` pushes current app
108+
locales to core using lookup strategy (`intlChangeLocale(Lookup(...))`)
109+
- Supported packaged locales are restricted by
110+
`vault-android/app/build.gradle.kts` `androidResources.localeFilters`
111+
112+
## Translation file flow
113+
114+
Core/web:
115+
116+
1. English source extraction -> `extracted.json`
117+
2. Weblate translates per locale by editing `extracted.json`
118+
3. Compile `extracted.json` -> `compiled.json` (FormatJS AST)
119+
4. Runtime reads `compiled.json`
120+
121+
iOS:
122+
123+
1. Xcode generates `Localizable.xcstrings`
124+
2. Export to `vault-ios/res/values*/strings.xml` for Weblate
125+
3. Import translated XML back into `Localizable.xcstrings`
126+
127+
Android:
128+
129+
1. Author/update `vault-android/.../res/values/strings.xml`
130+
2. Weblate manages `values-*/strings.xml`
131+
3. Android resource system resolves at runtime
132+
133+
## Updating translation sources when adding new strings
134+
135+
This workflow is required whenever new translatable strings are introduced.
136+
137+
Run it before merging any code that adds or changes translation keys. The
138+
`main` branch must always keep extracted/source translation files in sync with
139+
the codebase.
140+
141+
Required updates:
142+
143+
1. iOS:
144+
build the app so Xcode regenerates
145+
`vault-ios/VaultCommon/Resources/Localizable.xcstrings`
146+
2. Android:
147+
manually add new base entries to
148+
`vault-android/app/src/main/res/values/strings.xml`
149+
3. Run `make intl-extract` from repository root
150+
(this runs core/web/iOS extraction: `intl-core-extract`,
151+
`intl-web-extract`, and `intl-ios-extract`)
152+
153+
Commit these translation source updates in the same changeset as the
154+
feature/fix that introduced the strings.
155+
156+
## Updating from Weblate translations
157+
158+
Use this workflow when translators have delivered updates in Weblate.
159+
160+
Branch policy:
161+
162+
- Keep `weblate` clean and Weblate-owned (only commits pushed by Weblate).
163+
- Do not add manual compile commits to `weblate`.
164+
- Compile translations on an integration branch that will be used for PR.
165+
166+
Workflow:
167+
168+
1. Fetch latest remote refs:
169+
`git fetch origin`
170+
2. Create integration branch from `origin/weblate`:
171+
`git checkout -b update-translations-YYYYMMDD origin/weblate`
172+
3. Rebase integration branch onto latest `origin/main` (no merge commits):
173+
`git rebase origin/main`
174+
4. Compile catalogs on the integration branch:
175+
`make intl-compile`
176+
5. Commit compiled artifacts and push integration branch:
177+
`git add .`
178+
`git commit -m "intl: compile catalogs from latest weblate translations"`
179+
`git push -u origin update-translations-YYYYMMDD`
180+
6. Open a PR from the integration branch to `main`
181+
182+
This keeps the Weblate branch machine-managed while ensuring generated intl
183+
artifacts are reviewed in the PR.
184+
185+
## Commands
186+
187+
Top-level helpers:
188+
189+
```sh
190+
make intl-generate-icu-data
191+
make intl-extract
192+
make intl-compile
193+
```
194+
195+
Per-component commands:
196+
197+
```sh
198+
make intl-core-extract
199+
make intl-core-compile
200+
201+
make intl-web-extract
202+
make intl-web-compile
203+
204+
make intl-ios-extract
205+
make intl-ios-compile
206+
207+
# android does not need extract/compile
208+
```
209+
210+
Script mapping:
211+
212+
- Core extract: `vault-core/scripts/intl-extract.sh` (uses `vault-intl-extract`)
213+
- Core compile: `vault-core/scripts/intl-compile.sh` (FormatJS compile to AST)
214+
- Web extract: `vault-web/scripts/intl-extract.sh` (FormatJS extract)
215+
- Web compile: `vault-web/scripts/intl-compile.sh` (FormatJS compile to AST)
216+
- iOS extract/import: `vault-ios/scripts/xcstrings-convert.py`
217+
218+
## Locale source of truth
219+
220+
- Core locales list: `vault-core/src/intl/locales/locales.json`
221+
- Web locales list (used by landing page):
222+
`vault-web/src/features/intl/locales/locales.json`
223+
- iOS Xcode project: `vault-ios/Vault.xcodeproj/project.pbxproj`
224+
(`knownRegions`)
225+
- Android packaged locale filters:
226+
`vault-android/app/build.gradle.kts` (`androidResources.localeFilters`)
227+
228+
For adding a new locale, follow `docs/intl/add-new-language.md` (Weblate setup,
229+
locale registration, platform locale lists, ICU data regeneration, compilation).

0 commit comments

Comments
 (0)