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/package.json CHANGED
@@ -1,17 +1,27 @@
1
1
  {
2
2
  "name": "locon",
3
- "version": "1.4.4",
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
- "test": "jest",
14
- "publish": "npm publish"
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
- "jest-environment-jsdom": "^29.7.0",
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-native": "^0.73.0",
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-eslint": "^8.5.0",
62
- "typescript": "^5.8.3"
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.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: 'Locon Sync'
3
+ short_description: 'Audit and synchronize locon translation assets'
4
+ default_prompt: 'Use $locon-sync to audit and synchronize this project’s translation assets.'