@likolabs/i18nmd 0.1.1 → 0.3.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/CHANGELOG.md +19 -0
- package/PROMPT.md +2 -0
- package/README.haw.md +421 -0
- package/README.md +83 -8
- package/bin/i18nmd.mjs +91 -16
- package/lib/catalog.mjs +13 -8
- package/lib/compiler.mjs +156 -36
- package/lib/extractor.mjs +1 -1
- package/lib/html.mjs +309 -0
- package/lib/interop.mjs +4 -4
- package/lib/llm.mjs +1 -1
- package/lib/lock.mjs +18 -8
- package/lib/messages.mjs +12 -5
- package/lib/plural-rules.json +1 -0
- package/lib/runtime.mjs +24 -6
- package/package.json +10 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- **Fixed: Python plurals now match JavaScript.** The Python module used a table of whole numbers, so decimals always took `other` (French `1.5` is `one`) and large numbers could pick the wrong form (French `1000000` is `many`). It now evaluates CLDR's plural rules, the data behind `Intl.PluralRules`, with the same operands and rounding, and rounds numbers half away from zero as `Intl` does. Python numbers are read as doubles, like JavaScript's. A new cross-runtime test renders plurals, ordinals and every number style in 20 languages over 160 values and requires identical output.
|
|
6
|
+
- **Currencies in Python:** `{n, number, ::currency/EUR}` now writes the currency as `Intl` does.
|
|
7
|
+
- **App formatters:** `{len, length}` passes a value to a function the app supplies, for text ICU can't write, such as fractional inches. Declare the names in `i18nmd.lock.json` (`"formatters": ["length"]`), then `registerFormatter` (generated JavaScript), `createI18n(catalog, { formatters })`, or `register_formatter` (Python).
|
|
8
|
+
- **`has(token)`:** `i18nmd.has(token)`, `i18nmd.<division>.has(token)` and Python's `has(token)` say whether a token is compiled. A division can no longer be called `has`.
|
|
9
|
+
- **Fixed:** `import --out translations/<division>` wrote its own lock inside the division with unprefixed tokens, so `status` from the root never saw its translations. It now records them in the tree's lock.
|
|
10
|
+
- **`import --merge`** adds source-language messages to an existing source file without touching other languages or the lock. The README describes how to translate sentences stored in a database this way.
|
|
11
|
+
|
|
12
|
+
## 0.2.1
|
|
13
|
+
|
|
14
|
+
- **Python numbers follow each language:** grouping and decimal marks, Indian-style grouping, minus signs and percent signs come from `Intl` at compile time, so `{n, number}` and `#` match the JavaScript runtime.
|
|
15
|
+
- **README in ʻōlelo Hawaiʻi:** [README.haw.md](README.haw.md).
|
|
16
|
+
- **Fixed:** a translation edited before its source text changed was counted as updated for the new text, so the change went unnoticed. Now an edit only counts once `sync` (or `status`) has seen the source change; if both changed in between, the translation is marked stale for review. Existing lock files keep working.
|
|
17
|
+
|
|
18
|
+
## 0.2.0
|
|
19
|
+
|
|
20
|
+
- **Static websites.** `extract` reads `.html` pages: it marks each sentence, the title and description, image text and form labels with a `data-i18n` attribute and leaves the English in place, and takes `/* i18n */` strings from inline scripts. `render <site> --out dist --url …` writes a copy of every page in every language (`/haw/…`) with `lang`, `hreflang` links, `og:url`, adjusted relative links, and a language switcher wherever a page has `<nav data-i18n-languages>`. No JavaScript needed.
|
|
21
|
+
|
|
3
22
|
## 0.1.1
|
|
4
23
|
|
|
5
24
|
- Published to npm as `@likolabs/i18nmd` (`npm install --save-dev @likolabs/i18nmd`); the command is still `i18nmd`. npm 12 refuses GitHub and tarball URLs by default, so the 0.1.0 install instructions failed there.
|
package/PROMPT.md
CHANGED
|
@@ -11,6 +11,8 @@ Give these instructions to a coding agent in the application's repository. They
|
|
|
11
11
|
npx i18nmd extract src/components --out translations/ui --in-place
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
For a site of plain HTML pages, extract the pages (`npx i18nmd extract site --in-place`) and build each language with `npx i18nmd render site --out dist`.
|
|
15
|
+
|
|
14
16
|
3. **Review the extractor's work.** Read the diff. Then work through every diagnostic it printed:
|
|
15
17
|
- *looks like a count*: make the message an ICU plural, `{count, plural, one {# item} other {# items}}`.
|
|
16
18
|
- *text chosen in code*: move the wording into the message as an ICU `select` or plural, instead of passing English through a placeholder.
|
package/README.haw.md
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
# i18n.md
|
|
2
|
+
|
|
3
|
+
[English](README.md) · **ʻŌlelo Hawaiʻi**
|
|
4
|
+
|
|
5
|
+
**[E hoʻāʻo i ka hōʻike ola ma i18n.md](https://i18n.md)** · Hana ʻia e **[Liko Labs](https://likolabs.com)** ma Hilo, Hawaiʻi
|
|
6
|
+
|
|
7
|
+
E mālama i nā kikokikona o kāu polokalamu (app) a me kāu kahua pūnaewele ma Markdown: hoʻokahi waihona no kēlā me kēia ʻōlelo, i hiki i nā mea unuhi, nā mea nānā, a me nā LLM ke heluhelu a hoʻoponopono pololei. Nānā ka polokalamu hōʻuluʻulu (compiler) i nā ʻōlelo a pau, a hana i ke code i hoʻopaʻa ʻia nā ʻano (typed code) e hoʻohana ʻia e kāu polokalamu.
|
|
8
|
+
|
|
9
|
+
Ua hana ʻo [Liko Labs](https://likolabs.com) iā i18nmd a hāʻawi manuahi aku me ka manaʻolana e maʻalahi ka hoʻolako ʻana i ka ʻōlelo Hawaiʻi no kēlā me kēia ʻoihana a me nā hui kaiāulu ma Hawaiʻi. ʻEkolu wale nō kauoha no ke kahua pūnaewele ma ka ʻōlelo Pelekānia a me ka ʻōlelo Hawaiʻi, ʻo `extract`, `--add haw` a me `render` (e nānā i [Nā kahua pūnaewele HTML](#nā-kahua-pūnaewele-html)), a noho kēlā me kēia māmalaʻōlelo Hawaiʻi i loko o kekahi waihona maʻalahi e hiki ai i ka mea mākaukau ke nānā a hoʻoponopono. No ke kōkua i ka lawe ʻana mai i ka ʻōlelo Hawaiʻi i kāu kahua pūnaewele a i ʻole polokalamu, e kelepona aku iā [Liko Labs](https://likolabs.com).
|
|
10
|
+
|
|
11
|
+
````md
|
|
12
|
+
# Français
|
|
13
|
+
|
|
14
|
+
## cart_items
|
|
15
|
+
|
|
16
|
+
Context: Item count in the cart header.
|
|
17
|
+
|
|
18
|
+
```icu
|
|
19
|
+
{count, plural, one {# article} other {# articles}}
|
|
20
|
+
```
|
|
21
|
+
````
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
<p>{i18nmd('cart_items', { count })}</p>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
ʻO nā waihona ke kumu o ka ʻoiaʻiʻo. ʻIke ʻia nā loli i loko o nā pull request, a hiki i kekahi ke hāʻawi i kekahi waihona i ke kanaka a i ʻole ka LLM e unuhi. ʻO nā mea a pau a i18nmd e hana ai, he loli ia i ia mau waihona, a i ʻole he code i hana ʻia mai ia mau waihona.
|
|
28
|
+
|
|
29
|
+
- [Hoʻokomo](#hoʻokomo)
|
|
30
|
+
- [Hoʻomaka wikiwiki](#hoʻomaka-wikiwiki)
|
|
31
|
+
- [Nā kahua pūnaewele HTML](#nā-kahua-pūnaewele-html)
|
|
32
|
+
- [Nā waihona ʻōlelo](#nā-waihona-ʻōlelo)
|
|
33
|
+
- [Nā māhele](#nā-māhele)
|
|
34
|
+
- [Ka hoʻohana ʻana i ke code i hana ʻia](#ka-hoʻohana-ʻana-i-ke-code-i-hana-ʻia)
|
|
35
|
+
- [Ka huki ʻana i nā kikokikona mai kāu code](#ka-huki-ʻana-i-nā-kikokikona-mai-kāu-code)
|
|
36
|
+
- [Ka unuhi ʻana me ka LLM](#ka-unuhi-ʻana-me-ka-llm)
|
|
37
|
+
- [Ka mālama ʻana i nā unuhina i ke au](#ka-mālama-ʻana-i-nā-unuhina-i-ke-au)
|
|
38
|
+
- [Python](#python)
|
|
39
|
+
- [Nā ʻano waihona ʻē aʻe](#nā-ʻano-waihona-ʻē-aʻe)
|
|
40
|
+
- [Nā kauoha](#nā-kauoha)
|
|
41
|
+
- [Nā palena o kēia manawa](#nā-palena-o-kēia-manawa)
|
|
42
|
+
|
|
43
|
+
## Hoʻokomo
|
|
44
|
+
|
|
45
|
+
Pono ʻo i18nmd iā Node.js 22 a i ʻole ka mana hou aku.
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
npm install --save-dev @likolabs/i18nmd
|
|
49
|
+
npx i18nmd --version
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Hoʻomaka wikiwiki
|
|
53
|
+
|
|
54
|
+
**1. E huki i nā kikokikona** mai kekahi polokalamu React a i ʻole JSX/TSX. Me `--in-place`, kākau hou ʻo i18nmd i kāu mau waihona code, no laila e commit mua a nānā i nā loli.
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
npx i18nmd extract src --in-place
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Pae kāu mau kikokikona i `translations/i18n-en.md` (e hoʻohana iā `--source it` inā ma ka ʻōlelo ʻĪkālia kāu polokalamu), a kāhea kāu code i nā unuhina ma kahi o ka kikokikona:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// before
|
|
64
|
+
<p>Welcome back, {user.name}!</p>
|
|
65
|
+
// after
|
|
66
|
+
import { i18nmd } from '../i18n/i18n';
|
|
67
|
+
<p>{i18nmd('welcome_back', { name: user.name })}</p>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Hōʻike ka mea huki (extractor) i nā mea ʻaʻole hiki iā ia ke hoʻololi, e like me ke kikokikona i kūkulu ʻia ma ke code a i ʻole nā helu e pono ai ke ʻano lehulehu (plural). Aʻo ʻo [ka paipai huki](PROMPT.md) (extraction prompt) i kekahi coding agent pehea e hoʻopau ai i ka hana.
|
|
71
|
+
|
|
72
|
+
**2. E hoʻohui i nā ʻōlelo.** E noi i kekahi LLM ma o ka CLI, a i ʻole e kākau iho i nā waihona iā ʻoe iho:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
export ANTHROPIC_API_KEY=… # or OPENAI_API_KEY, or I18NMD_* (see below)
|
|
76
|
+
npx i18nmd --add french --add german
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**3. E hōʻuluʻulu (compile)** i `src/i18n`. E hoʻohui iā ia i kāu kūkulu ʻana (build) i hoʻohana kēlā me kēia kūkulu ʻana i nā unuhina hou loa:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
npx i18nmd compile
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{ "scripts": { "prebuild": "i18nmd compile" } }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**4. E ʻae i ka mea heluhelu e koho i ka ʻōlelo.** Unuhi nā kāhea i ka ʻōlelo o kēia manawa, ʻo ia hoʻi ka ʻōlelo o ka polokalamu kele pūnaewele (browser) o ka mea heluhelu ma ka hoʻomaka ʻana:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { useSyncExternalStore } from 'react';
|
|
93
|
+
import { languages, getLanguage, setLanguage, onLanguageChange } from './i18n/language.mjs';
|
|
94
|
+
|
|
95
|
+
export function LanguagePicker() {
|
|
96
|
+
const language = useSyncExternalStore(onLanguageChange, getLanguage);
|
|
97
|
+
return (
|
|
98
|
+
<select value={language} onChange={e => void setLanguage(e.target.value)}>
|
|
99
|
+
{Object.entries(languages).map(([code, name]) => <option key={code} value={code} lang={code}>{name}</option>)}
|
|
100
|
+
</select>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**5. E mālama i ke au.** Ma hope o ka hoʻoponopono ʻana i ke kikokikona kumu, hōʻike ʻo `npx i18nmd status` i nā mea e pono ai ka unuhi, a hoʻopiha ʻo `npx i18nmd translate` iā lākou.
|
|
106
|
+
|
|
107
|
+
## Nā kahua pūnaewele HTML
|
|
108
|
+
|
|
109
|
+
ʻAʻole pono ke JavaScript no ke kahua pūnaewele i kūkulu ʻia me nā ʻaoʻao HTML. Kākau ʻo i18nmd i kope o kēlā me kēia ʻaoʻao ma kēlā me kēia ʻōlelo:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
npx i18nmd extract site --in-place # site/*.html → translations/i18n-en.md
|
|
113
|
+
npx i18nmd --add haw # or write translations/i18n-haw.md yourself
|
|
114
|
+
npx i18nmd render site --out dist --url https://example.com # dist/ and dist/haw/
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Kau ʻo `extract` i ka hōʻailona `data-i18n` ma kēlā me kēia ʻāpana kikokikona, a waiho i ka ʻōlelo Pelekānia ma kona wahi, no laila wehe a hoʻoponopono ʻia ka ʻaoʻao e like me ma mua:
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<h1 data-i18n="from_first_leaf_to_full_grown">From first leaf to <em class="red">full grown.</em></h1>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Mālama ka memo i ka māmalaʻōlelo holoʻokoʻa, me kona mau hōʻailona HTML i loko (inline markup) ma ke ʻano he mau tag: `From first leaf to <em>full grown.</em>`. Hoʻoneʻe ka mea unuhi i ka tag; noho kona class a me kona mau ʻano ʻē aʻe (attributes) ma ka ʻaoʻao. Lawe pū ʻo `extract` i ka `<title>` o ka ʻaoʻao, nā hōʻailona `<meta>` no ka wehewehe a me ka hōʻike loulou, nā attribute `alt`, `title`, `placeholder` a me `aria-label`, a me nā kikokikona i loko o nā script i kaha ʻia me `/* i18n */`:
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
statusEl.textContent = /* i18n */ 'Sending…';
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Lele ʻo ia ma luna o nā helu wahi (address) e like me `example.com`, a me nā mea a pau i loko o kekahi element me `translate="no"`, ka attribute maʻamau no nā inoa a me ke code. Papa inoa ʻo ia i ke kikokikona ʻaʻole hiki iā ia ke hoʻonoho, e like me nā huaʻōlelo ma ka ʻaoʻao o kekahi element poloka, a me nā kikokikona script e like ana me nā mea e heluhelu ai ke kanaka.
|
|
130
|
+
|
|
131
|
+
Noho ka ʻōlelo Pelekānia i kāu HTML. E hoʻoponopono i kekahi ʻaoʻao, e holo hou iā `extract`, a hahai ka waihona ʻōlelo kumu; lilo nā mana o ia kikokikona ma nā ʻōlelo ʻē aʻe i **kahiko** (stale), no ka `translate` e hoʻohou ai.
|
|
132
|
+
|
|
133
|
+
Kākau ʻo `render` i ka ʻōlelo kumu ma kahi o nā ʻaoʻao, a me kēlā me kēia ʻōlelo ʻē aʻe i loko o kekahi waihona i kapa ʻia ma kona inoa, `/haw/`, me ke kope pū ʻana i nā mea ʻē aʻe a pau o ke kahua pūnaewele. Loaʻa i kēlā me kēia kope:
|
|
134
|
+
|
|
135
|
+
- ke kikokikona i unuhi ʻia, a noho ke kikokikona i unuhi ʻole ʻia ma ka ʻōlelo kumu
|
|
136
|
+
- `<html lang>`, a me `dir="rtl"` no nā ʻōlelo i kākau ʻia mai ka ʻākau a i ka hema
|
|
137
|
+
- `<link rel="alternate" hreflang>` i ke kope o kēlā me kēia ʻōlelo, i hōʻiliʻili nā mīkini huli i kēlā me kēia
|
|
138
|
+
- me `--url`, kuhikuhi nā loulou `og:url` a me canonical i ia kope
|
|
139
|
+
- hoʻoponopono ʻia nā loulou pili (relative links) no ka waihona o ka ʻōlelo
|
|
140
|
+
|
|
141
|
+
E kau iā `<nav data-i18n-languages></nav>` ma kekahi wahi o ka ʻaoʻao, a hoʻopiha ʻo `render` iā ia me ka loulou i kēlā me kēia ʻōlelo, i kapa ʻia ma ia ʻōlelo. E hoʻolaha (deploy) iā `dist/`.
|
|
142
|
+
|
|
143
|
+
## Nā waihona ʻōlelo
|
|
144
|
+
|
|
145
|
+
Paʻa ʻo `translations/i18n-fr.md` i ka ʻōlelo Palani wale nō. Hāʻawi ka inoa waihona i ke code ʻōlelo (`fr`, `pt-BR`, a i ʻole kekahi inoa i haku ʻia e like me `pirate`). Kapa ke poʻo inoa mua i ka ʻōlelo ma ia ʻōlelo nō, a he inoa memo (token) kēlā me kēia poʻo inoa `##`:
|
|
146
|
+
|
|
147
|
+
````md
|
|
148
|
+
# Français
|
|
149
|
+
|
|
150
|
+
## cart_items
|
|
151
|
+
|
|
152
|
+
Context: Item count in the cart header.
|
|
153
|
+
|
|
154
|
+
```icu
|
|
155
|
+
{count, plural, one {# article} other {# articles}}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## greeting
|
|
159
|
+
|
|
160
|
+
Optional: a
|
|
161
|
+
|
|
162
|
+
```icu
|
|
163
|
+
Bonjour {name} !
|
|
164
|
+
```
|
|
165
|
+
````
|
|
166
|
+
|
|
167
|
+
- Wehewehe ʻo **Context** (pōʻaiapili) i kahi e ʻike ʻia ai ka kikokikona a me nā mea e pono ai ka mea unuhi: ke ʻano o ka leo, nā palena o ka lōʻihi, nā mea ʻaʻole e unuhi ʻia.
|
|
168
|
+
- Papa inoa ʻo **Optional** i nā hakahaka (placeholders) e hiki ke haʻalele ʻia e ka unuhina, e like me ka ʻatikala Pelekānia (`{a}` no "a" a i ʻole "an") ʻaʻole pono i nā ʻōlelo ʻē aʻe.
|
|
169
|
+
- ʻO ka memo he [ICU MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/): nā hakahaka me ka inoa `{name}`, `plural`, `selectordinal`, `select`, `number` (`integer`, `percent`, `::currency/EUR`), `date` a me `time` me kekahi ʻano, a me nā tag e like me `<b>…</b>`.
|
|
170
|
+
- He ʻokina wale nō ka ʻokina, a pēlā nō ka apostrophe. No ka kākau ʻana i ka kahakaha (brace) maoli, a i ʻole `<` ma mua o kekahi hua palapala, e hoʻopuni iā ia me ka apostrophe: `'{'`, `'<'`.
|
|
171
|
+
|
|
172
|
+
ʻO ia ke ʻano holoʻokoʻa. Noho ka moʻokāki ma ka ʻaoʻao o nā waihona ma `i18nmd.lock.json`, e hoʻopaʻa ana i ka ʻōlelo kumu a me nā unuhina e kūpono ana i ke au.
|
|
173
|
+
|
|
174
|
+
Hoʻohana kēlā me kēia waihona ʻōlelo i nā inoa memo a me nā hakahaka like me ke kumu. Nānā ʻo `i18nmd check` iā lākou a pau: hōʻike ʻia ka unuhina me ka hakahaka nalo a i ʻole ʻike ʻole ʻia, ka plural haki, a i ʻole ka tag kūlike ʻole, a hoʻohana ke kūkulu ʻana i ke kikokikona kumu a hoʻoponopono ʻia. ʻAʻole hāʻule ke kūkulu ʻana ma muli o nā unuhina nalo a kahiko paha.
|
|
175
|
+
|
|
176
|
+
## Nā māhele
|
|
177
|
+
|
|
178
|
+
Hiki i ka polokalamu nui ke hoʻokaʻawale i kona mau kikokikona i nā māhele, hoʻokahi waihona (directory) no kēlā me kēia, ma ke ʻano e kūpono ai i ka poʻe e hoʻoponopono ana:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
translations/
|
|
182
|
+
i18nmd.lock.json
|
|
183
|
+
account/i18n-en.md account/i18n-fr.md
|
|
184
|
+
ui/i18n-en.md ui/i18n-fr.md
|
|
185
|
+
marketing/i18n-en.md marketing/i18n-fr.md
|
|
186
|
+
marketing/landing/i18n-en.md
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
He waihona kumu a me nā unuhina ko kēlā me kēia māhele, a hoʻokumu ʻia nā inoa memo ma kona ala. ʻO `## hero` i loko o `marketing/i18n-en.md` ka inoa memo `marketing.hero`:
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import { i18nmd } from './i18n/marketing';
|
|
193
|
+
import './i18n/marketing.landing';
|
|
194
|
+
import './i18n/knowledge-hub';
|
|
195
|
+
|
|
196
|
+
i18nmd.marketing('hero') // or i18nmd('marketing.hero')
|
|
197
|
+
i18nmd.marketing.landing('save') // marketing/landing/
|
|
198
|
+
i18nmd.knowledgeHub('search') // knowledge-hub/ becomes camelCase
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Mālama nā waihona i nā inoa pōkole, no laila hiki i nā māhele ʻelua ke loaʻa he `save`. E kapa i nā waihona māhele me nā hua palapala, nā helu, `_` a me `-`; hōʻole ʻia nā inoa i loaʻa i nā function a pau (`name`, `length`, `call`, …) a me `in`.
|
|
202
|
+
|
|
203
|
+
Hoʻokaʻawale pū nā māhele i kāu pūʻolo (bundle): hōʻuluʻulu ʻia kēlā me kēia i kona module ponoʻī, a hoʻouna kāu bundler iā ia me ke code e hoʻohana ana iā ia. E kau i nā kikokikona e pono ai kēlā me kēia ʻaoʻao, e like me ke komo ʻana (sign-in) a me nā ʻaoʻao hoʻouka, i loko o kekahi māhele liʻiliʻi ponoʻī, i lawe ka hoʻoili mua i kēlā mau mea wale nō.
|
|
204
|
+
|
|
205
|
+
Pili nā kauoha ma `translations/` i nā māhele a pau, a hōʻike ʻo `status` i ka holomua ma kēlā me kēia māhele. E hāʻawi i ka waihona o kekahi māhele e hana ma luna ona wale nō, e like me `i18nmd translate translations/marketing`. Noho ka lock ma luna a kaʻana like ʻia.
|
|
206
|
+
|
|
207
|
+
## Ka hoʻohana ʻana i ke code i hana ʻia
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
npx i18nmd compile # translations/ → src/i18n
|
|
211
|
+
npx i18nmd compile --out lib/i18n # elsewhere
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Ke kāhea ʻana i nā unuhina
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import { i18nmd } from './i18n/i18n';
|
|
218
|
+
|
|
219
|
+
i18nmd('cart_items', { count: 3 }) // top-level token
|
|
220
|
+
i18nmd.ui('save') // a division's token
|
|
221
|
+
i18nmd.in('fr').ui('save') // a fixed language
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Nānā ʻo TypeScript i kēlā me kēia inoa memo a me kona mau waiwai: he hewa compile ka waiwai nalo, ke ʻano hewa, a i ʻole ka inoa memo mai kekahi māhele ʻē. ʻAe pū ka hakahaka maʻamau iā `null` a i ʻole `undefined` a hōʻike ʻole i kekahi mea, e like me JSX.
|
|
225
|
+
|
|
226
|
+
Lawe mai (import) ke code e kāhea ana i kekahi māhele iā `i18nmd` mai ka module o ia māhele (`./i18n/ui`). Lawe mai pū ka waihona e kāhea ana i ka māhele ʻelua i kona module, `import './i18n/marketing'`. Kākau ʻo `extract` i kēia mau import. Hāʻule ʻo `npx i18nmd check --in src` me ka laina e hoʻohui ai inā nalo kekahi, a hoʻohui ʻo `--fix` iā ia.
|
|
227
|
+
|
|
228
|
+
Unuhi ʻia nā kikokikona i loko o ke code e holo hoʻokahi wale nō, e like me ka constant ma ka pae module, i ia manawa nō. E hoʻopuni iā lākou i loko o kekahi function i hiki aku ka loli ʻōlelo iā lākou:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
export const steps = () => [i18nmd.ui('measure'), i18nmd.ui('cut')];
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Ka ʻōlelo o kēia manawa
|
|
235
|
+
|
|
236
|
+
Hoʻomaka ka ʻōlelo o kēia manawa me ka koho mua o ka mea heluhelu, a laila nā ʻōlelo o kona polokalamu kele pūnaewele, a laila ka ʻōlelo kumu. Mālama ʻo `language.mjs` iā ia a paʻa ʻole i nā memo, no laila hiki i kāu code komo (entry code) ke lawe mai iā ia me ke kaumaha ʻole:
|
|
237
|
+
|
|
238
|
+
| Export | Hana |
|
|
239
|
+
| --- | --- |
|
|
240
|
+
| `languages` | Kēlā me kēia code ʻōlelo a me kona inoa ma ia ʻōlelo, no ka papa koho. |
|
|
241
|
+
| `getLanguage()` | Ka ʻōlelo o kēia manawa. |
|
|
242
|
+
| `setLanguage(code)` | Hoʻouka i ka ʻōlelo, hoʻololi iā ia a hoʻomanaʻo. ʻAe ʻia ka like kokoke: koho ʻo `pt` iā `pt-BR`. |
|
|
243
|
+
| `onLanguageChange(listener)` | Kāhea i ka listener ma hope o kēlā me kēia loli; hoʻihoʻi i ka function e hoʻōki ai. |
|
|
244
|
+
| `ready` | Hoʻoholo ʻia ke hoʻouka ʻia ka ʻōlelo o ka mea heluhelu. |
|
|
245
|
+
| `loadLanguage(code)` | Hoʻouka i kekahi ʻōlelo no `i18nmd.in(code)`, ma ke kikowaena (server) a i ʻole nā hoʻāʻo. |
|
|
246
|
+
|
|
247
|
+
He ʻāpana ponoʻī kēlā me kēia unuhina, no laila hoʻoili ka mea heluhelu i kāna ʻōlelo wale nō. E hōʻike ma hope o `ready` i ʻike ka mea heluhelu i koho i ka ʻōlelo Palani i ka ʻōlelo Palani mai ka hōʻike mua loa; no ka ʻōlelo kumu, hoʻoholo koke ʻia:
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import { ready, getLanguage, onLanguageChange } from './i18n/language.mjs';
|
|
251
|
+
|
|
252
|
+
function Localized() {
|
|
253
|
+
const language = useSyncExternalStore(onLanguageChange, getLanguage);
|
|
254
|
+
return <App key={language} />; // re-render everything in the new language
|
|
255
|
+
}
|
|
256
|
+
ready.then(() => createRoot(root).render(<Localized />));
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Huli ka ʻimi ʻōlelo i ke code pololei (`fr-CA`), a laila ka ʻōlelo kumu o ia code (`fr`), a laila ka ʻōlelo kumu o ka polokalamu. I loko o ka memo, hoʻi ka unuhina nalo i ke kikokikona kumu.
|
|
260
|
+
|
|
261
|
+
### Ke kikokikona waiwai (rich text)
|
|
262
|
+
|
|
263
|
+
Noho nā hōʻailona i loko o ka māmalaʻōlelo, i hiki i nā mea unuhi ke hoʻonohonoho hou iā lākou:
|
|
264
|
+
|
|
265
|
+
```icu
|
|
266
|
+
Read <link>the guide</link> before <b>{date, date, long}</b>.
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
i18nmd('read_the_guide', {
|
|
271
|
+
date,
|
|
272
|
+
link: chunks => <a key="link" href="/guide">{chunks}</a>,
|
|
273
|
+
b: chunks => <b key="b">{chunks}</b>,
|
|
274
|
+
})
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Hoʻihoʻi nā memo me nā tag i kekahi array, a hōʻike pololei ʻo React iā ia.
|
|
278
|
+
|
|
279
|
+
### Nā mea a `compile` e kākau ai
|
|
280
|
+
|
|
281
|
+
| Waihona | Paʻa |
|
|
282
|
+
| --- | --- |
|
|
283
|
+
| `i18n.ts` | Nā ʻano (types), `i18nmd`, a me nā inoa memo ma ka pae kiʻekiʻe. |
|
|
284
|
+
| `<division>.ts` | Nā memo ʻōlelo kumu o hoʻokahi māhele. |
|
|
285
|
+
| `language.mjs` | Ka ʻōlelo o kēia manawa a me nā mea hoʻouka, me ka memo ʻole. |
|
|
286
|
+
| `languages/<code>.mjs` | Nā memo a pau ma hoʻokahi ʻōlelo ʻē. |
|
|
287
|
+
| `runtime.mjs` | Ka mea hoʻonohonoho (formatter), e hoʻohana ana i ka `Intl` o ka polokalamu kele pūnaewele no nā plural, nā helu a me nā lā. |
|
|
288
|
+
|
|
289
|
+
E commit iā lākou a i ʻole e hana iā lākou i kāu kūkulu ʻana; holoi ʻo `compile` i nā waihona āna i hana ai ma mua akā ʻaʻole e kākau hou. Kau ʻo `--eager` i nā memo a me nā ʻōlelo a pau i loko o `i18n.ts`, no nā kikowaena, nā hoʻāʻo a me nā polokalamu liʻiliʻi. Kākau ʻo `--target js` i nā waihona like ma JavaScript, kākau ʻo `--target json` i hoʻokahi waihona JSON o ke kikokikona memo, a haʻalele ʻo `--skip <division>` i nā māhele e hoʻohana ʻia e kekahi polokalamu ʻē.
|
|
290
|
+
|
|
291
|
+
### Nā hewa i ka holo ʻana
|
|
292
|
+
|
|
293
|
+
ʻAʻole e hāʻule ka ʻaoʻao ma muli o ka inoa memo ʻike ʻole ʻia a i ʻole ka waiwai nalo. Kākau ka runtime iā ia i ka moʻolelo (log) a hōʻike i ka inoa memo a i ʻole `{placeholder}` ma kahi ona. Haʻi ka inoa memo mai kekahi māhele i lawe ʻole ʻia mai e ka ʻaoʻao i ka import e hoʻohui ai.
|
|
294
|
+
|
|
295
|
+
## Ka huki ʻana i nā kikokikona mai kāu code
|
|
296
|
+
|
|
297
|
+
```sh
|
|
298
|
+
npx i18nmd extract src --in-place # everything → translations/
|
|
299
|
+
npx i18nmd extract src/account src/routes.tsx --out translations/account --in-place
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Heluhelu ʻo `extract` i JavaScript a me TypeScript, me JSX a i ʻole me ka ʻole, a hoʻoneʻe i kēia mau mea i ka waihona ʻōlelo kumu:
|
|
303
|
+
|
|
304
|
+
- Ke kikokikona JSX, me ka mālama ʻana i kēlā me kēia māmalaʻōlelo holoʻokoʻa. Lilo nā waiwai i loko o ka māmalaʻōlelo i mau hakahaka me ka inoa: lilo ʻo `{formatLength(kerf)}` iā `{kerf}`, a lilo ʻo `{items.length}` iā `{itemsCount}`.
|
|
305
|
+
- Nā element i loko o ka laina e like me `<b>`, `<a href>` a me `<Link to>`, e lilo ana i mau tag.
|
|
306
|
+
- Nā huaʻōlelo i koho ʻia ma ke code: lilo ʻo `{busy ? "Saving…" : "Save"}` i ʻelua memo.
|
|
307
|
+
- Nā attribute i ʻike ʻia: `alt`, `title`, `placeholder`, `label`, `aria-label`, `aria-description`.
|
|
308
|
+
- Kēlā me kēia kikokikona i kaha ʻia me `/* i18n */`, a i ʻole `/* i18n:token_name */` e koho ai i kona inoa memo.
|
|
309
|
+
|
|
310
|
+
Waiho ʻo ia i nā helu, nā hōʻailona a me ke kikokikona hua palapala ʻole, a me nā mea ʻaʻole hiki iā ia ke hoʻololi me ka palekana; papa inoa ʻia kēlā mau mea me ko lākou waihona a me ka laina. Hōʻailona pū ʻo ia i nā helu e pono ai ke ʻano plural a me nā huaʻōlelo i kūkulu ʻia ma ke code e pono ai ke lilo i ICU `select`.
|
|
311
|
+
|
|
312
|
+
Hele mai nā inoa memo mai nā huaʻōlelo o ka memo, e like me `welcome_back`, a ʻaʻole loli ke hoʻoponopono ʻoe i ke kikokikona. Ke holo hou ʻia ʻo `extract`, mālama ʻia nā inoa memo a me nā unuhina a pau, a hoʻohui ʻia nā kikokikona hou wale nō. Me ka ʻole o `--in-place`, kākau ʻo ia i nā kope i hoʻololi ʻia i `--dest` (ʻo `.i18n/src` ma ka maʻamau) a waiho i kāu mau waihona kumu. Huki ʻo `--out translations/<division>` i loko o kekahi māhele.
|
|
313
|
+
|
|
314
|
+
Mālama ka mea huki i ka hana mīkini. He paipai ʻo [PROMPT.md](PROMPT.md) no kekahi coding agent e hana i ke koena: nā kikokikona i loko o nā waihona `.ts` maʻamau a me nā object, nā māmalaʻōlelo i kūkulu ʻia ma ke code, a me ka nānā ʻana i kēlā me kēia ʻaoʻao.
|
|
315
|
+
|
|
316
|
+
## Ka unuhi ʻana me ka LLM
|
|
317
|
+
|
|
318
|
+
```sh
|
|
319
|
+
npx i18nmd --add french # also: Français, fr, pt-BR, "brazilian portuguese", klingon
|
|
320
|
+
npx i18nmd --add pirate # anything unrecognized becomes a custom style
|
|
321
|
+
npx i18nmd --top 10 # the 10 most widely spoken languages (i18nmd languages lists them)
|
|
322
|
+
npx i18nmd translate # fill every missing or outdated translation
|
|
323
|
+
npx i18nmd translate --only fr,de --dry-run
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
E hoʻonoho i kekahi o kēia:
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
export ANTHROPIC_API_KEY=… # Claude
|
|
330
|
+
export OPENAI_API_KEY=… OPENAI_BASE_URL=… # OpenAI or any compatible API
|
|
331
|
+
export I18NMD_BASE_URL=… I18NMD_API_KEY=… I18NMD_MODEL=… # overrides both
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
ʻIke ʻia ka mea hoʻolako (provider) mai ka URL a i ʻole ke kī; hoʻololi ʻo `--provider`, `--base-url`, `--model` a me `--batch` iā ia no kēlā me kēia holo.
|
|
335
|
+
|
|
336
|
+
Hele nā memo ma nā pūʻulu, me ka laina pōʻaiapili o kēlā me kēia, nā unuhina i loaʻa ma ke ʻano he papa huaʻōlelo, a me ka unuhina mua o ka memo i loli. Nānā ʻia kēlā me kēia pane e like me ka unuhina i kākau ʻia e ka lima. Hoʻāʻo hou ʻia ka memo i hāʻule hoʻokahi manawa me ka hewa, a hōʻike ʻia nā mea e hāʻule mau ana a hoʻi i ka ʻōlelo kumu. Mālama ʻia ka holomua ma hope o kēlā me kēia pūʻulu, no laila ʻaʻohe mea e nalo ke ʻoki ʻia ka holo ʻana.
|
|
337
|
+
|
|
338
|
+
## Ka mālama ʻana i nā unuhina i ke au
|
|
339
|
+
|
|
340
|
+
```sh
|
|
341
|
+
npx i18nmd status
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
English (en): 1847 tokens, source
|
|
346
|
+
Français (fr): 1790/1847 done, 40 missing, 17 stale
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Lilo ka unuhina i **kahiko** (stale) ke loli kona kikokikona kumu ma hope o kona kākau ʻia ʻana. E hoʻoponopono i ka unuhina, a helu ʻo i18nmd iā ia he mea i hoʻohou ʻia; ʻaʻohe hōʻailona e holoi ai. Hoʻopiha ʻo `translate` i nā unuhina nalo a kahiko.
|
|
350
|
+
|
|
351
|
+
| Kauoha | Hana |
|
|
352
|
+
| --- | --- |
|
|
353
|
+
| `status` | Ka holomua no kēlā me kēia ʻōlelo a me kēlā me kēia māhele. |
|
|
354
|
+
| `check` | Nānā i kēlā me kēia waihona. Hāʻule pū ʻo `--strict` ma nā mea nalo a kahiko paha, no CI ma mua o ka hoʻopuka ʻana. Nānā ʻo `--in src` inā lawe mai kāu code i nā māhele āna e kāhea ai, a papa inoa i nā inoa memo i kāhea ʻole ʻia. |
|
|
355
|
+
| `sync` | Hoʻohou iā `i18nmd.lock.json` a holoi i nā inoa memo ʻaʻole i loaʻa hou ma ke kumu mai nā ʻōlelo ʻē aʻe. |
|
|
356
|
+
| `rename <old> <new> --in src` | Kapa hou i kekahi inoa memo ma nā ʻōlelo a pau, ka lock, a me nā kāhea i kāu code. |
|
|
357
|
+
| `join --out all.md` | Kākau i nā ʻōlelo a pau i hoʻokahi waihona Markdown, e nānā ai ma ka ʻaoʻao kekahi i kekahi a i ʻole e hāʻawi ai i ka LLM. |
|
|
358
|
+
| `split all.md --out translations` | Hoʻokaʻawale hou i ka waihona i hui ʻia i nā waihona ʻōlelo. |
|
|
359
|
+
|
|
360
|
+
ʻImi nā kauoha iā `translations/`, `i18n/`, `locales/` a i ʻole ka waihona o kēia manawa iā lākou iho; e hāʻawi i ke ala a i ʻole `--dir` no nā wahi ʻē aʻe. ʻAe pū kēlā me kēia kauoha i ka waihona i hui ʻia ma kahi o ka waihona (directory).
|
|
361
|
+
|
|
362
|
+
## Python
|
|
363
|
+
|
|
364
|
+
```sh
|
|
365
|
+
npx i18nmd compile translations/server --target python --out app/i18n
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
from app.i18n.i18n import i18nmd, template, LANGS
|
|
370
|
+
|
|
371
|
+
i18nmd("server.parts", "de", n=3) # "3 Teile"
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
ʻAʻohe mea e pili ai (dependencies) ka module Python. Hoʻonohonoho ʻo ia i nā hakahaka, nā plural, nā helu kaʻina (ordinal), nā select a me nā helu, me nā lula plural a me ke ʻano helu o kēlā me kēia ʻōlelo (`1.234,5` ma ka ʻōlelo Kelemānia, `12,34,567` ma ka ʻōlelo Hindi) i lawe ʻia mai ka `Intl` o kāu Node.js i ka manawa compile. Hoʻihoʻi ʻo `template(token, language)` i ka memo me nā hakahaka i hōʻike ʻia e like me `{name}`. Hoʻololi ʻo `import-python <module.py> --out translations` i kekahi papa unuhina Python i loaʻa.
|
|
375
|
+
|
|
376
|
+
## Nā ʻano waihona ʻē aʻe
|
|
377
|
+
|
|
378
|
+
```sh
|
|
379
|
+
npx i18nmd import locales/*/translation.json --from i18next
|
|
380
|
+
npx i18nmd import lang/en.json lang/fr.json --from formatjs
|
|
381
|
+
npx i18nmd export --to next-intl --out messages # messages/fr.json
|
|
382
|
+
npx i18nmd export --to formatjs --out lang # lang/fr.json, for react-intl
|
|
383
|
+
npx i18nmd export --to i18next --out locales # locales/fr/translation.json
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Hoʻohana mua ʻo FormatJS a me next-intl iā ICU, no laila ʻaʻohe mea e nalo i ka hoʻololi ʻana, a lilo nā wehewehe FormatJS i mau laina pōʻaiapili. No i18next, lilo ʻo `{{name}}` iā `{name}`, lilo nā hope plural i nā ICU plural, a lilo nā kī i hoʻopūnana ʻia i nā inoa memo me ke kiko. Mālama ʻia nā memo ʻaʻole hiki iā i18next ke hōʻike ma ke ʻano he kikokikona ICU no ka plugin i18next-icu, me ka ʻōlelo aʻo. Hiki iā ʻoe ke hoʻoponopono ma i18nmd a hoʻouna i nā mea a kāu polokalamu e hoʻouka nei.
|
|
387
|
+
|
|
388
|
+
## Nā kauoha
|
|
389
|
+
|
|
390
|
+
| Kauoha | |
|
|
391
|
+
| --- | --- |
|
|
392
|
+
| `render <site>` | `--out dist`, `--url https://example.com` |
|
|
393
|
+
| `extract <src…>` | `--in-place`, `--out translations/<division>`, `--source en`, `--runtime src/i18n/i18n`, `--dest .i18n/src`, `--locale-expr locale` (calls use `i18nmd.in(locale)`) |
|
|
394
|
+
| `compile` | `--out src/i18n`, `--target ts\|js\|json\|python`, `--eager`, `--skip <division,…>` |
|
|
395
|
+
| `--add <language>`, `--top <n>`, `translate` | `--only fr,de`, `--dry-run`, `--provider`, `--base-url`, `--model`, `--batch 40` |
|
|
396
|
+
| `status`, `check`, `sync` | `--strict`, `--in src`, `--fix`, `--skip` |
|
|
397
|
+
| `rename <old> <new>` | `--in src` |
|
|
398
|
+
| `join`, `split` | `--out` |
|
|
399
|
+
| `import`, `export`, `import-python` | `--from`, `--to`, `--out` |
|
|
400
|
+
| `languages` | Ka papa inoa a `--top` e hoʻohana ai. |
|
|
401
|
+
|
|
402
|
+
Lawe kēlā me kēia kauoha iā `--dir`, a me `--source` e hoʻololi ai i ka ʻōlelo kumu i hoʻopaʻa ʻia ma ka lock. Papa inoa ʻo `npx i18nmd --help` i nā mea a pau.
|
|
403
|
+
|
|
404
|
+
## Nā palena o kēia manawa
|
|
405
|
+
|
|
406
|
+
- Heluhelu ka mea huki i HTML, JavaScript a me TypeScript. Pono nā kikokikona i loko o nā object (`{ label: "Save" }`) a me nā waihona `.ts` maʻamau iā `/* i18n */` a i ʻole ka paipai huki.
|
|
407
|
+
- Hoʻouka ka hoʻololi ʻana i ka ʻōlelo i ka ʻōlelo holoʻokoʻa i ka manawa hoʻokahi, ʻaʻole ma kēlā me kēia māhele.
|
|
408
|
+
- Kākau ka pahuhopu Python i nā lā e like me ka mea i hāʻawi ʻia, a me ke kikokikona o ka tag me ka hōʻailona ʻole, ke hāʻawi ʻole ʻoe i kekahi function nona.
|
|
409
|
+
|
|
410
|
+
## Ka hoʻomohala ʻana
|
|
411
|
+
|
|
412
|
+
```sh
|
|
413
|
+
npm ci --ignore-scripts
|
|
414
|
+
npm test
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Hoʻohana nā hoʻāʻo i kekahi mea pani kūloko no nā API LLM ʻelua, no laila ʻaʻole pono lākou i nā kī a ʻaʻole hoʻopili i ka pūnaewele.
|
|
418
|
+
|
|
419
|
+
## Laikini
|
|
420
|
+
|
|
421
|
+
MIT © [Liko Labs](https://likolabs.com)
|
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# i18n.md
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**[Try the live demo at i18n.md](https://i18n.md)** · Made by **[Liko Labs](https://likolabs.com)** in Hilo, Hawaiʻi · [ʻŌlelo Hawaiʻi](README.haw.md)
|
|
4
|
+
|
|
5
|
+
Keep your interface strings in Markdown, for apps and for plain HTML sites: one file per language, readable and editable by translators, reviewers and LLMs. A compiler checks every language and generates typed code your app imports.
|
|
6
|
+
|
|
7
|
+
[Liko Labs](https://likolabs.com) created i18nmd and makes it freely available in the hope of making ʻōlelo Hawaiʻi easy to offer for any business or community group in Hawaiʻi. A website in English and Hawaiian is three commands, `extract`, `--add haw` and `render` (see [Static websites](#static-websites)), and every Hawaiian sentence stays in a plain file a fluent speaker can review and correct. For help bringing ʻōlelo Hawaiʻi to your site or app, contact [Liko Labs](https://likolabs.com).
|
|
4
8
|
|
|
5
9
|
````md
|
|
6
10
|
# Français
|
|
@@ -22,16 +26,18 @@ The files are the source of truth. They diff in pull requests, and anyone can ha
|
|
|
22
26
|
|
|
23
27
|
- [Install](#install)
|
|
24
28
|
- [Quick start](#quick-start)
|
|
29
|
+
- [Static websites](#static-websites)
|
|
25
30
|
- [Language files](#language-files)
|
|
26
31
|
- [Divisions](#divisions)
|
|
27
32
|
- [Using the generated code](#using-the-generated-code)
|
|
28
33
|
- [Extracting strings from your code](#extracting-strings-from-your-code)
|
|
29
34
|
- [Translating with an LLM](#translating-with-an-llm)
|
|
30
35
|
- [Keeping translations current](#keeping-translations-current)
|
|
36
|
+
- [Text from a database](#text-from-a-database)
|
|
31
37
|
- [Python](#python)
|
|
32
38
|
- [Other formats and libraries](#other-formats-and-libraries)
|
|
33
39
|
- [Command reference](#command-reference)
|
|
34
|
-
- [
|
|
40
|
+
- [Current limits](#current-limits)
|
|
35
41
|
|
|
36
42
|
## Install
|
|
37
43
|
|
|
@@ -97,6 +103,42 @@ export function LanguagePicker() {
|
|
|
97
103
|
|
|
98
104
|
**5. Keep it current.** After editing source text, `npx i18nmd status` shows what needs translating and `npx i18nmd translate` fills it in.
|
|
99
105
|
|
|
106
|
+
## Static websites
|
|
107
|
+
|
|
108
|
+
A site made of HTML pages needs no JavaScript at all. i18nmd writes a copy of each page in each language:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
npx i18nmd extract site --in-place # site/*.html → translations/i18n-en.md
|
|
112
|
+
npx i18nmd --add haw # or write translations/i18n-haw.md yourself
|
|
113
|
+
npx i18nmd render site --out dist --url https://example.com # dist/ and dist/haw/
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`extract` marks each piece of text with a `data-i18n` attribute and leaves the English where it is, so the page still opens and edits as before:
|
|
117
|
+
|
|
118
|
+
```html
|
|
119
|
+
<h1 data-i18n="from_first_leaf_to_full_grown">From first leaf to <em class="red">full grown.</em></h1>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The message keeps the sentence whole, with its inline markup as tags: `From first leaf to <em>full grown.</em>`. Translators move the tag; its class and other attributes stay in the page. Extract also takes the page `<title>`, the description and link-preview `<meta>` tags, `alt`, `title`, `placeholder` and `aria-label` attributes, and strings in inline scripts marked `/* i18n */`:
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
statusEl.textContent = /* i18n */ 'Sending…';
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
It skips addresses such as `example.com`, and anything inside an element with `translate="no"`, the standard attribute for names and code. It lists text it can't place, such as words beside a block element, and script strings that look like text people read.
|
|
129
|
+
|
|
130
|
+
The English lives in your HTML. Edit a page, run `extract` again, and the source language file follows; the other languages' versions of that text become stale for `translate` to update.
|
|
131
|
+
|
|
132
|
+
`render` writes the source language where the pages are and every other language in a directory named for it, `/haw/`, copying everything else in the site alongside. Each copy gets:
|
|
133
|
+
|
|
134
|
+
- the translated text, with untranslated text left in the source language
|
|
135
|
+
- `<html lang>`, and `dir="rtl"` for right-to-left languages
|
|
136
|
+
- `<link rel="alternate" hreflang>` to every language's copy, so search engines index each one
|
|
137
|
+
- with `--url`, `og:url` and canonical links pointing at that copy
|
|
138
|
+
- relative links adjusted for the language directory
|
|
139
|
+
|
|
140
|
+
Put `<nav data-i18n-languages></nav>` anywhere on a page, and render fills it with a link to each language, named in that language. Deploy `dist/`.
|
|
141
|
+
|
|
100
142
|
## Language files
|
|
101
143
|
|
|
102
144
|
`translations/i18n-fr.md` holds French and nothing else. The filename gives the language code (`fr`, `pt-BR`, or a custom name such as `pirate`). The first heading names the language in that language, and each `##` heading is a token:
|
|
@@ -124,6 +166,7 @@ Bonjour {name} !
|
|
|
124
166
|
- **Context** explains where a string appears and anything a translator needs: tone, length limits, what not to translate.
|
|
125
167
|
- **Optional** lists placeholders a translation may leave out, such as an English article (`{a}` for "a" or "an") that other languages don't need.
|
|
126
168
|
- The message is [ICU MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/): named placeholders `{name}`, `plural`, `selectordinal`, `select`, `number` (`integer`, `percent`, `::currency/EUR`), `date` and `time` with a style, and tags such as `<b>…</b>`.
|
|
169
|
+
- **App formatters** cover values ICU can't write, such as `1-1/2"`: `{len, length}` passes `len` to a function your app supplies. Declare the names once in `i18nmd.lock.json`, `"formatters": ["length"]`, and `check` and `compile` reject any other type. Translators may move the placeholder but not change its type; put unit notes in the context line. The same value can also choose a plural branch, `{len, plural, …}`.
|
|
127
170
|
- An apostrophe is just an apostrophe. To write a literal brace, or `<` before a letter, quote it: `'{'`, `'<'`.
|
|
128
171
|
|
|
129
172
|
That is the whole format. Bookkeeping lives beside the files in `i18nmd.lock.json`, which records the source language and which translations are current.
|
|
@@ -233,6 +276,18 @@ i18nmd('read_the_guide', {
|
|
|
233
276
|
|
|
234
277
|
Messages with tags return an array, which React renders directly.
|
|
235
278
|
|
|
279
|
+
### App formatters
|
|
280
|
+
|
|
281
|
+
Register each formatter the lock declares before rendering. It receives the value and the language being written, and returns text:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
import { registerFormatter } from './i18n/i18n';
|
|
285
|
+
|
|
286
|
+
registerFormatter('length', (inches, language) => language === 'en' ? toFractionalInches(inches) : `${Math.round(inches * 25.4)} mm`);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
i18nmd ships no formatters; your app decides what `length` means. A missing or failing formatter is reported like other runtime errors and the raw value is shown.
|
|
290
|
+
|
|
236
291
|
### What compile writes
|
|
237
292
|
|
|
238
293
|
| File | Holds |
|
|
@@ -249,6 +304,8 @@ Commit these or generate them in your build; compile removes files it generated
|
|
|
249
304
|
|
|
250
305
|
An unknown token or a missing value never crashes the page. The runtime logs it and shows the token name or `{placeholder}` instead. A token from a division the page never imported says which import to add.
|
|
251
306
|
|
|
307
|
+
`i18nmd.has('cart_items')`, or `i18nmd.ui.has('save')` for a division, says whether a token is in the compiled catalog without logging anything, so code can tell text added since the last compile from a mistake.
|
|
308
|
+
|
|
252
309
|
## Extracting strings from your code
|
|
253
310
|
|
|
254
311
|
```sh
|
|
@@ -316,6 +373,20 @@ A translation is **stale** when its source text changed after it was written. Ed
|
|
|
316
373
|
|
|
317
374
|
Commands find `translations/`, `i18n/`, `locales/` or the current directory on their own; pass a path or `--dir` for anywhere else. Every command also accepts a joined file in place of a directory.
|
|
318
375
|
|
|
376
|
+
## Text from a database
|
|
377
|
+
|
|
378
|
+
Sentences stored in a database, such as narration written by people or agents, are translated through the same files. i18nmd has no database adapter and never translates at request time; the Markdown files and the lock are the cache, so each sentence is translated once.
|
|
379
|
+
|
|
380
|
+
1. A job in your app writes new or changed sentences as FormatJS JSON, `{"step_42": {"defaultMessage": "Cut the board to {len, length}.", "description": "Narration for step 42"}}`, and merges them into a division's source file:
|
|
381
|
+
|
|
382
|
+
```sh
|
|
383
|
+
npx i18nmd import sentences.json --from formatjs --merge --out translations/procedures
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
`--merge` appends new tokens and replaces changed text. It removes nothing and leaves other languages and the lock alone, so `status` lists new sentences as missing and changed ones as stale.
|
|
387
|
+
2. `npx i18nmd translate` fills the other languages, and `npx i18nmd compile` builds the catalog.
|
|
388
|
+
3. Until a sentence is translated, the runtime shows it in the source language. Until it is compiled, `i18nmd.has(token)` is false and the app shows its own copy of the text.
|
|
389
|
+
|
|
319
390
|
## Python
|
|
320
391
|
|
|
321
392
|
```sh
|
|
@@ -328,7 +399,9 @@ from app.i18n.i18n import i18nmd, template, LANGS
|
|
|
328
399
|
i18nmd("server.parts", "de", n=3) # "3 Teile"
|
|
329
400
|
```
|
|
330
401
|
|
|
331
|
-
The Python module has no dependencies
|
|
402
|
+
The Python module has no dependencies, and it renders every message exactly as the JavaScript runtime does: placeholders, plurals, ordinals, selects and numbers, including decimals such as French `1,5`, which takes the `one` form. Plural rules come from CLDR, the data `Intl` uses, and each language's number style (`1.234,5` in German, `12,34,567` in Hindi, currencies) from your Node.js's `Intl` at compile time. Python numbers are read as doubles, as JavaScript reads them. A test renders a spread of languages and values in both runtimes and requires identical output. That parity holds against CLDR 48 (Node.js 26); a browser with older CLDR data can differ from the Python rules in rare cases, which is a difference in `Intl` versions, not a bug in i18nmd.
|
|
403
|
+
|
|
404
|
+
`template(token, language)` returns the message with placeholders shown as `{name}`, `has(token)` says whether a token is in the module, and `register_formatter("length", fn)` supplies an app formatter, where `fn(value, language)` returns text. Keep each formatter the same in both languages; i18nmd can only promise identical output for its own formatting. `import-python <module.py> --out translations` converts an existing literal Python translation table.
|
|
332
405
|
|
|
333
406
|
## Other formats and libraries
|
|
334
407
|
|
|
@@ -346,22 +419,24 @@ FormatJS and next-intl already use ICU, so conversion is lossless, and FormatJS
|
|
|
346
419
|
|
|
347
420
|
| Command | |
|
|
348
421
|
| --- | --- |
|
|
422
|
+
| `render <site>` | `--out dist`, `--url https://example.com` |
|
|
349
423
|
| `extract <src…>` | `--in-place`, `--out translations/<division>`, `--source en`, `--runtime src/i18n/i18n`, `--dest .i18n/src`, `--locale-expr locale` (calls use `i18nmd.in(locale)`) |
|
|
350
424
|
| `compile` | `--out src/i18n`, `--target ts\|js\|json\|python`, `--eager`, `--skip <division,…>` |
|
|
351
425
|
| `--add <language>`, `--top <n>`, `translate` | `--only fr,de`, `--dry-run`, `--provider`, `--base-url`, `--model`, `--batch 40` |
|
|
352
426
|
| `status`, `check`, `sync` | `--strict`, `--in src`, `--fix`, `--skip` |
|
|
353
427
|
| `rename <old> <new>` | `--in src` |
|
|
354
428
|
| `join`, `split` | `--out` |
|
|
355
|
-
| `import`, `export`, `import-python` | `--from`, `--to`, `--out` |
|
|
429
|
+
| `import`, `export`, `import-python` | `--from`, `--to`, `--out`, `--merge` |
|
|
356
430
|
| `languages` | The ranked list `--top` uses. |
|
|
357
431
|
|
|
358
432
|
Every command takes `--dir`, and `--source` to override the source language recorded in the lock. `npx i18nmd --help` lists everything.
|
|
359
433
|
|
|
360
|
-
##
|
|
434
|
+
## Current limits
|
|
361
435
|
|
|
362
|
-
- The extractor reads JavaScript and TypeScript. Strings in object properties (`{ label: "Save" }`) and plain `.ts` files need `/* i18n */` or the extraction prompt.
|
|
436
|
+
- The extractor reads HTML, JavaScript and TypeScript. Strings in object properties (`{ label: "Save" }`) and plain `.ts` files need `/* i18n */` or the extraction prompt.
|
|
363
437
|
- Switching language loads that whole language at once, not per division.
|
|
364
|
-
- The Python target
|
|
438
|
+
- The Python target writes dates as given, and a tag's text without its markup unless you pass a function for it.
|
|
439
|
+
- App formatters are yours to keep identical in JavaScript and Python.
|
|
365
440
|
|
|
366
441
|
## Development
|
|
367
442
|
|
|
@@ -374,4 +449,4 @@ The tests use a local stand-in for both LLM APIs, so they need no keys and make
|
|
|
374
449
|
|
|
375
450
|
## License
|
|
376
451
|
|
|
377
|
-
MIT
|
|
452
|
+
MIT © [Liko Labs](https://likolabs.com)
|