locon 1.4.4 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +498 -36
- package/dist/index.d.ts +187 -10
- package/dist/index.js +510 -75
- package/package.json +36 -12
- package/skills/locon-sync/SKILL.md +158 -0
- package/skills/locon-sync/agents/openai.yaml +4 -0
- package/skills/locon-sync/scripts/check-locon-assets.mjs +594 -0
- package/skills/locon-sync/scripts/write_locale.py +229 -0
- package/dist/components/LText.d.mts +0 -10
- package/dist/components/LText.d.ts +0 -10
- package/dist/components/LText.js +0 -41
- package/dist/components/LText.mjs +0 -11
- package/dist/hooks/useLocon.d.mts +0 -6
- package/dist/hooks/useLocon.d.ts +0 -6
- package/dist/hooks/useLocon.js +0 -30
- package/dist/hooks/useLocon.mjs +0 -10
- package/dist/index.d.mts +0 -19
- package/dist/index.mjs +0 -104
- package/dist/types/Assets.d.mts +0 -3
- package/dist/types/Assets.d.ts +0 -3
- package/dist/types/Assets.js +0 -16
- package/dist/types/Assets.mjs +0 -0
- package/dist/types/LoconContext.d.mts +0 -12
- package/dist/types/LoconContext.d.ts +0 -12
- package/dist/types/LoconContext.js +0 -16
- package/dist/types/LoconContext.mjs +0 -0
- package/dist/utils/getSystemLanguage.d.mts +0 -7
- package/dist/utils/getSystemLanguage.d.ts +0 -7
- package/dist/utils/getSystemLanguage.js +0 -125
- package/dist/utils/getSystemLanguage.mjs +0 -105
package/package.json
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "locon",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "locon is a simple and lightweight library for React Native that provides a simple way to manage translations in your app.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.mjs",
|
|
7
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./skills/*": "./skills/*",
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
8
15
|
"files": [
|
|
9
|
-
"dist"
|
|
16
|
+
"dist",
|
|
17
|
+
"skills"
|
|
10
18
|
],
|
|
11
19
|
"scripts": {
|
|
12
20
|
"build": "tsup",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
21
|
+
"lint": "eslint src",
|
|
22
|
+
"prepare": "tsup",
|
|
23
|
+
"pretest": "tsup",
|
|
24
|
+
"test": "jest"
|
|
15
25
|
},
|
|
16
26
|
"keywords": [
|
|
17
27
|
"i18n",
|
|
@@ -19,6 +29,7 @@
|
|
|
19
29
|
"react-native",
|
|
20
30
|
"react native",
|
|
21
31
|
"reactjs",
|
|
32
|
+
"expo",
|
|
22
33
|
"localization",
|
|
23
34
|
"globalization",
|
|
24
35
|
"translations",
|
|
@@ -28,12 +39,24 @@
|
|
|
28
39
|
],
|
|
29
40
|
"author": "loconapp",
|
|
30
41
|
"license": "ISC",
|
|
42
|
+
"homepage": "https://locon.dev",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/loconapp/locon.git"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/loconapp/locon/issues"
|
|
49
|
+
},
|
|
31
50
|
"peerDependencies": {
|
|
51
|
+
"expo-localization": ">=14.0.0",
|
|
32
52
|
"react": ">=19.0.0",
|
|
33
53
|
"react-native": ">=0.70.0",
|
|
34
54
|
"react-native-localize": "^3.0.0"
|
|
35
55
|
},
|
|
36
56
|
"peerDependenciesMeta": {
|
|
57
|
+
"expo-localization": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
37
60
|
"react-native-localize": {
|
|
38
61
|
"optional": true
|
|
39
62
|
}
|
|
@@ -44,21 +67,22 @@
|
|
|
44
67
|
"@testing-library/jest-dom": "^6.5.0",
|
|
45
68
|
"@testing-library/react": "^16.0.1",
|
|
46
69
|
"@types/jest": "^29.5.12",
|
|
47
|
-
"@types/react-dom": "^19.1.0",
|
|
48
70
|
"@types/react": "^19.1.1",
|
|
71
|
+
"@types/react-dom": "^19.1.0",
|
|
49
72
|
"@types/react-native": "^0.73.0",
|
|
50
73
|
"babel-jest": "^29.7.0",
|
|
51
|
-
"eslint-plugin-react": "^7.35.2",
|
|
52
74
|
"eslint": "^9.10.0",
|
|
53
|
-
"
|
|
75
|
+
"eslint-plugin-react": "^7.35.2",
|
|
76
|
+
"globals": "^14.0.0",
|
|
54
77
|
"jest": "^29.7.0",
|
|
78
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
55
79
|
"prettier": "3.4.1",
|
|
56
|
-
"react-dom": "^19.1.1",
|
|
57
80
|
"react": "^19.1.1",
|
|
58
|
-
"react-
|
|
81
|
+
"react-dom": "^19.1.1",
|
|
82
|
+
"react-native": "0.81.5",
|
|
59
83
|
"ts-jest": "^29.2.5",
|
|
60
84
|
"tsup": "^8.2.4",
|
|
61
|
-
"typescript
|
|
62
|
-
"typescript": "^5.
|
|
85
|
+
"typescript": "^5.8.3",
|
|
86
|
+
"typescript-eslint": "^8.5.0"
|
|
63
87
|
}
|
|
64
88
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: locon-sync
|
|
3
|
+
description: Synchronize locon translation assets across every language a project supports. Audit locale JSON against code and declared languages; verify key parity, placeholders, CLDR plurals, duplicate keys, and script integrity; generate safe locale files. Use when adding a language, adding or changing UI strings, fixing missing translations, or auditing any app that uses locon.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Syncing locon translation assets
|
|
7
|
+
|
|
8
|
+
locon resolves a string by its **value in the project language**, not by a key:
|
|
9
|
+
`<LText>Guten Morgen</LText>` looks up that exact phrase. That makes the assets
|
|
10
|
+
unusually easy to write and unusually easy to break silently — a typo in a
|
|
11
|
+
phrase is not a compile error, it just renders the source language to someone
|
|
12
|
+
who does not read it.
|
|
13
|
+
|
|
14
|
+
This skill audits and fills the locale files so that never happens.
|
|
15
|
+
|
|
16
|
+
## 1. Read the project's setup first
|
|
17
|
+
|
|
18
|
+
Never assume paths. Find them:
|
|
19
|
+
|
|
20
|
+
- **Assets directory** — the per-locale JSON (often `src/i18n/assets/`).
|
|
21
|
+
- **Project language** — the `projectLocale` prop on `<Locon>`. Its file is the
|
|
22
|
+
source of truth; every other file is derived from it.
|
|
23
|
+
- **Default language** — the `defaultLocale` prop, used when a key is missing.
|
|
24
|
+
- **Declared languages** — the list the app ships. Cross-check it against the
|
|
25
|
+
files on disk in _both_ directions: a declared language with no file renders
|
|
26
|
+
entirely in the source language.
|
|
27
|
+
|
|
28
|
+
Resolve the directory containing this `SKILL.md` as
|
|
29
|
+
`LOCON_SYNC_SKILL_DIR`. Never assume the current working directory contains the
|
|
30
|
+
skill. Then run the audit from the app's root and pass every declared locale:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
node "$LOCON_SYNC_SKILL_DIR/scripts/check-locon-assets.mjs" \
|
|
34
|
+
--source de \
|
|
35
|
+
--locales de,en,fr
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This scans static strings in JavaScript/TypeScript (single, double, or template
|
|
39
|
+
quotes), `lIn()` calls and `LText` children/`assetKey` props. It ignores calls
|
|
40
|
+
inside comments and string examples. Orphaned keys and plural categories that
|
|
41
|
+
currently fall back to `_other` are advisory because static analysis cannot
|
|
42
|
+
prove either is wrong. Enforce them in CI with `--strict-orphans` and
|
|
43
|
+
`--strict-plurals` when the project keeps both explicit.
|
|
44
|
+
|
|
45
|
+
Pass `--allow-keys key_a,key_b` only for deliberate direct-key disambiguation.
|
|
46
|
+
Pass `--allow-implicit-count ar:day_one,ar:day_two` only when those exact
|
|
47
|
+
translations state the number in words and therefore intentionally omit
|
|
48
|
+
`{count}`. Treat an unused allow-list entry as stale configuration.
|
|
49
|
+
|
|
50
|
+
## 2. The invariants
|
|
51
|
+
|
|
52
|
+
Every one of these has broken a real project.
|
|
53
|
+
|
|
54
|
+
**Key parity.** Every locale carries every key the source has.
|
|
55
|
+
|
|
56
|
+
**No duplicate source values.** Two keys with the same source phrase means only
|
|
57
|
+
the _first_ is reachable by value lookup. The second is dead weight translators
|
|
58
|
+
will fill in for nothing, and any difference between the two translations
|
|
59
|
+
silently never appears. Either merge them, or keep both and have the code
|
|
60
|
+
address the shadowed one by key (`assetKey`) — the one legitimate reason to
|
|
61
|
+
write a key in application code.
|
|
62
|
+
|
|
63
|
+
**Plural categories are CLDR names,** never a generic `_plural`: `_zero`,
|
|
64
|
+
`_one`, `_two`, `_few`, `_many`, `_other`, exactly what
|
|
65
|
+
`Intl.PluralRules.select()` returns. Languages do not have "a" plural — German
|
|
66
|
+
needs two forms, Polish and Russian three, Arabic up to six. **A target locale
|
|
67
|
+
may add categories the source language lacks**; that is correct, not an error.
|
|
68
|
+
`_other` is the mandatory catch-all, and also what English uses as its plural.
|
|
69
|
+
|
|
70
|
+
**Placeholders match exactly.** Allow a missing `{count}` only for an explicit
|
|
71
|
+
locale/key pair whose words state the exact number. Arabic `يوم واحد` and
|
|
72
|
+
Hebrew `נותרה דקה אחת` already mean "one day" / "one minute"; repeating the
|
|
73
|
+
digit reads as "1 one day". Never infer this from `_one`: Russian selects
|
|
74
|
+
`_one` for 21, so `day_one: "день"` would incorrectly render just "день".
|
|
75
|
+
A translation may never introduce a placeholder the source lacks.
|
|
76
|
+
|
|
77
|
+
**No foreign scripts.** Machine-assisted translation occasionally drops a
|
|
78
|
+
fragment of the wrong alphabet into a string — a CJK word inside Russian prose.
|
|
79
|
+
A native reviewer reads that as file corruption rather than a translation
|
|
80
|
+
error, and nothing else in the toolchain catches it.
|
|
81
|
+
|
|
82
|
+
**Brand names and endonyms are never translated.** Product names stay verbatim;
|
|
83
|
+
language names in a picker are always endonyms, because a row reading
|
|
84
|
+
"Turkish" is useless to the person who needs it.
|
|
85
|
+
|
|
86
|
+
## 3. Filling in a locale
|
|
87
|
+
|
|
88
|
+
Put the completed target values in a temporary flat JSON object, then generate
|
|
89
|
+
the locale with the bundled CLI so key order, grouping and parity are
|
|
90
|
+
guaranteed by construction:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
python3 "$LOCON_SYNC_SKILL_DIR/scripts/write_locale.py" \
|
|
94
|
+
--locale fr \
|
|
95
|
+
--input /tmp/fr.json \
|
|
96
|
+
--assets src/i18n/assets \
|
|
97
|
+
--source de
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
For an intentional implicit number, add for example
|
|
101
|
+
`--allow-implicit-count day_one,day_two`. The writer validates before an atomic
|
|
102
|
+
replace, copies the source file's blank-line grouping, accepts target-specific
|
|
103
|
+
plural categories, and refuses unsafe locale names, duplicate JSON keys,
|
|
104
|
+
parity errors, or placeholder drift.
|
|
105
|
+
|
|
106
|
+
Translate **meaning, not words**. UI copy has length constraints, and a literal
|
|
107
|
+
rendering of a German compound will overflow a button in Finnish. Keep the
|
|
108
|
+
register of the source — if the app addresses the user informally, match that
|
|
109
|
+
per language convention (`du` / `tu` / `ты`).
|
|
110
|
+
|
|
111
|
+
## 4. What the code should look like
|
|
112
|
+
|
|
113
|
+
Confirm the code addresses strings by source phrase. Keys in application code
|
|
114
|
+
defeat the point of the library:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
l('E-Mail nicht verfügbar') // ✅
|
|
118
|
+
l('settings_mail_unavailable_title') // ❌ unless deliberately disambiguating
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Two mechanical rules:
|
|
122
|
+
|
|
123
|
+
- **`l('…')`** for anything taking a string — props, `Alert.alert`, and the
|
|
124
|
+
children of components that wrap text themselves. A `<Button>` rendering
|
|
125
|
+
`<Text>{children}</Text>` must not receive `<LText>`, or you nest Text in Text.
|
|
126
|
+
- **`<LText>`** only where the code would otherwise render a bare `<Text>`.
|
|
127
|
+
|
|
128
|
+
When extracting phrases from source, remember **JSX collapses whitespace**: a
|
|
129
|
+
multi-line `<LText>` child becomes one space-joined string at runtime, so the
|
|
130
|
+
asset value must match the collapsed form.
|
|
131
|
+
|
|
132
|
+
## 5. Beyond the strings
|
|
133
|
+
|
|
134
|
+
Shipping a language is more than its JSON. Check whether the project needs:
|
|
135
|
+
|
|
136
|
+
- **Fonts for the new scripts.** Most Latin webfonts carry no Cyrillic, Greek,
|
|
137
|
+
Arabic, Hebrew, Devanagari, Thai or CJK glyphs, and a loaded font missing a
|
|
138
|
+
glyph renders a tofu box on Android rather than falling back. Registering a
|
|
139
|
+
script's font _under the app's existing family names_ fixes every screen
|
|
140
|
+
without touching a single StyleSheet. For CJK, loading nothing is often the
|
|
141
|
+
right answer: those families are tens of megabytes, and an unregistered
|
|
142
|
+
family name falls back to the platform's own system face.
|
|
143
|
+
- **Calendars and numerals.** `Intl` defaults Persian to the Jalali calendar
|
|
144
|
+
and Arabic to Arabic-Indic digits. If the data model is Gregorian, say so:
|
|
145
|
+
`intlLocale(code, { calendar: 'gregory', numbering: 'latn' })`. Otherwise a
|
|
146
|
+
Persian document is headed with a month its own contents do not contain.
|
|
147
|
+
- **RTL.** `isRtlLocale()` tells you the direction; `applyRTL()` applies it and
|
|
148
|
+
returns whether a restart is needed. Direction only takes effect after that
|
|
149
|
+
restart, so prompt rather than flipping silently, and convert hardcoded
|
|
150
|
+
`left`/`right` style props to `start`/`end`.
|
|
151
|
+
- **Store and native metadata.** In Expo, the `expo-localization` plugin's
|
|
152
|
+
`supportedLocales` writes `CFBundleLocalizations` and `locales_config.xml`,
|
|
153
|
+
which is what makes the stores list the languages and what gives Android 13+
|
|
154
|
+
its per-app language picker. Translated permission strings go in `locales`.
|
|
155
|
+
- **Which languages the stores actually accept.** A bundled translation is
|
|
156
|
+
always allowed, but a _store listing_ is not: the App Store and Google Play
|
|
157
|
+
support overlapping-but-different sets. Ship what you like; just do not
|
|
158
|
+
promise a store page you cannot publish.
|