@ng-linguo/linguo 0.9.0 → 0.9.2
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 +391 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
GENERATED FILE — do not edit.
|
|
3
|
+
Synced from the repository root README.md by tools/sync-readme.mjs.
|
|
4
|
+
Edit /README.md instead; this copy is regenerated on every linguo build.
|
|
5
|
+
-->
|
|
6
|
+
|
|
1
7
|
<p align="center">
|
|
2
8
|
<img
|
|
3
9
|
src="https://raw.githubusercontent.com/jmwierzbicki/linguo/main/apps/playground/public/linguo-logo.png"
|
|
@@ -6,20 +12,396 @@
|
|
|
6
12
|
/>
|
|
7
13
|
</p>
|
|
8
14
|
|
|
9
|
-
#
|
|
15
|
+
# ng-linguo
|
|
16
|
+
|
|
17
|
+
**Signal-native internationalization for Angular.** A modern, complete i18n
|
|
18
|
+
toolkit for Angular 18+, built on SignalStore — an independent, from-scratch
|
|
19
|
+
alternative to `@ngx-translate/core` and Transloco, reactive by default with
|
|
20
|
+
zero RxJS plumbing in your components.
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<!-- translators edit plain text; this renders a real Angular link -->
|
|
24
|
+
<p t="Read the [docs]documentation[/docs] to get started">
|
|
25
|
+
<ng-template tFor="docs" let-text><a routerLink="/docs">{{ text }}</a></ng-template>
|
|
26
|
+
</p>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
<p align="center">
|
|
30
|
+
<a href="https://jmwierzbicki.github.io/linguo/">
|
|
31
|
+
<img
|
|
32
|
+
src="https://raw.githubusercontent.com/jmwierzbicki/linguo/main/apps/playground/public/linguo-demo.gif"
|
|
33
|
+
alt="Switching languages in the ng-linguo playground — every example re-renders reactively"
|
|
34
|
+
width="800"
|
|
35
|
+
/>
|
|
36
|
+
</a>
|
|
37
|
+
<br />
|
|
38
|
+
<em>Switch the language and every binding re-renders — no reload, no subscriptions.</em>
|
|
39
|
+
<br />
|
|
40
|
+
<strong><a href="https://jmwierzbicki.github.io/linguo/">▶ Try the live demo</a></strong>
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
> **Status:** pre-release (`0.9.x`) — published to npm and usable today. The
|
|
44
|
+
> runtime, the extraction CLI, and the full test suite are in place and green.
|
|
45
|
+
> APIs may still shift before `1.0`.
|
|
46
|
+
|
|
47
|
+
## Why ng-linguo
|
|
48
|
+
|
|
49
|
+
**Writing code**
|
|
50
|
+
|
|
51
|
+
- **Signals, not subscriptions.** Translations are reactive through
|
|
52
|
+
[`@ngrx/signals`](https://ngrx.io/guide/signals) — switch language and the UI
|
|
53
|
+
updates on its own, with no `async` pipe and no manual `subscribe`/`unsubscribe`.
|
|
54
|
+
- **Three ways to translate, one for each job.** A `t` pipe for templates, a
|
|
55
|
+
`[t]` directive for elements and rich text, and `injectTranslate()` for
|
|
56
|
+
TypeScript — see [which to use](#which-api-should-i-use).
|
|
57
|
+
- **Zoneless-ready, SSR-friendly, tree-shakeable.** No `zone.js` dependency,
|
|
58
|
+
safe to render on the server, and the optional ICU and HTTP pieces live in
|
|
59
|
+
separate entry points so you only ship what you import.
|
|
60
|
+
|
|
61
|
+
**Writing translations**
|
|
62
|
+
|
|
63
|
+
- **English _is_ the key — no key files to maintain.** You write real English
|
|
64
|
+
in your components, and that text _is_ the translation key. There are no
|
|
65
|
+
`home.header.title` paths to invent, keep unique, and keep in sync, and
|
|
66
|
+
nothing opaque for a translator (or an LLM) to guess at — they always see a
|
|
67
|
+
full, meaningful sentence. For the rare clash, a `context` disambiguates
|
|
68
|
+
(`Play` in a game vs. a music player).
|
|
69
|
+
- **Translators never see HTML.** Named slots `[name]…[/name]` (a BBCode-like
|
|
70
|
+
syntax) bind to _your_ `<ng-template>`, so links, buttons, and bindings render
|
|
71
|
+
as real Angular while the translation file stays plain text. Translated text
|
|
72
|
+
is never inserted as HTML, so cross-site scripting (XSS) is impossible by design.
|
|
73
|
+
- **Correct grammar in every language (ICU MessageFormat 2, and MF1).** Real
|
|
74
|
+
plurals, `select`, and gendered text per locale — Polish gets four plural
|
|
75
|
+
forms, English gets two, all from one message.
|
|
76
|
+
|
|
77
|
+
**Shipping translations**
|
|
78
|
+
|
|
79
|
+
- **A real, additive extraction pipeline.** Extract your source strings to
|
|
80
|
+
standard gettext `.po` files (works with Crowdin / Lokalise / Phrase) and
|
|
81
|
+
compile them to runtime JSON. Re-running extraction is _additive_: new and
|
|
82
|
+
changed strings merge in while every existing translation is kept.
|
|
83
|
+
- **Add a language in seconds.** Add its code to the config and extract — with
|
|
84
|
+
an AI translator wired up, filling it in is a single command (or a couple of
|
|
85
|
+
clicks in the interactive CLI).
|
|
86
|
+
- **Translate with AI — your model, your key.** Copy a ready-made prompt into
|
|
87
|
+
any chat model, or point ng-linguo at a translator module that calls your own
|
|
88
|
+
provider. ng-linguo writes the prompt (it teaches the model your context,
|
|
89
|
+
slot tags, and plural rules) and merges the reply; your SDK and API key never
|
|
90
|
+
leave your machine.
|
|
91
|
+
- **Automatable in CI.** Every command runs non-interactively and is
|
|
92
|
+
deterministic, so extraction and compilation drop straight into a pipeline.
|
|
93
|
+
|
|
94
|
+
**Fast by default**
|
|
95
|
+
|
|
96
|
+
- The `t` pipe memoizes its result, `injectTranslate()` + `computed()` does zero
|
|
97
|
+
work per change-detection pass, and ICU messages are compiled once and cached.
|
|
98
|
+
See [Performance](#performance).
|
|
99
|
+
|
|
100
|
+
## Install
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm i @ng-linguo/linguo @ngrx/signals
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Requires **Angular 18+**. `@ngrx/signals` is a peer dependency — bring your own.
|
|
107
|
+
|
|
108
|
+
## Getting started
|
|
109
|
+
|
|
110
|
+
The fastest path from an empty Angular app to a translated one. Steps 1–3 get
|
|
111
|
+
the runtime working; step 4 generates the real translation files.
|
|
112
|
+
|
|
113
|
+
### 1. Configure the runtime
|
|
114
|
+
|
|
115
|
+
Add the providers to your `app.config.ts` (or `bootstrapApplication`). Pick a
|
|
116
|
+
loader — loading is explicit, so nothing is fetched during DI setup.
|
|
10
117
|
|
|
11
|
-
|
|
118
|
+
Most apps load their translation JSON over HTTP:
|
|
12
119
|
|
|
13
120
|
```ts
|
|
14
121
|
import { provideTranslate } from '@ng-linguo/linguo';
|
|
15
|
-
import { provideIcu } from '@ng-linguo/linguo/icu';
|
|
16
122
|
import { createHttpLoader } from '@ng-linguo/linguo/http';
|
|
123
|
+
import { provideIcu } from '@ng-linguo/linguo/icu';
|
|
124
|
+
import { provideHttpClient } from '@angular/common/http';
|
|
125
|
+
|
|
126
|
+
export const appConfig = {
|
|
127
|
+
providers: [
|
|
128
|
+
provideHttpClient(),
|
|
129
|
+
provideTranslate({
|
|
130
|
+
defaultLang: 'en', // required: reported before load, and the fallback
|
|
131
|
+
// optional: only matches a saved/browser language to one you ship.
|
|
132
|
+
// This is a runtime concern — separate from the CLI's linguo.config.json.
|
|
133
|
+
supportedLangs: ['en', 'pl', 'de'],
|
|
134
|
+
// factory form: the loader is built in DI, so it can use HttpClient.
|
|
135
|
+
// GETs /assets/i18n/<lang>.json by default.
|
|
136
|
+
loader: () => createHttpLoader(),
|
|
137
|
+
}),
|
|
138
|
+
provideIcu(), // optional — enables ICU MessageFormat (defaults to MF2)
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Prefer to **bundle** translations (no network)? A loader is just an object with
|
|
144
|
+
a `load(lang)` method, so a static import works too:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import en from './i18n/en.json';
|
|
148
|
+
import pl from './i18n/pl.json';
|
|
149
|
+
|
|
150
|
+
const dictionaries: Record<string, unknown> = { en, pl };
|
|
151
|
+
|
|
152
|
+
provideTranslate({
|
|
153
|
+
defaultLang: 'en',
|
|
154
|
+
loader: { load: (lang) => Promise.resolve(dictionaries[lang] ?? {}) },
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 2. Load a language at startup
|
|
159
|
+
|
|
160
|
+
The store never loads on its own. Call `restoreLang()` once, usually in your
|
|
161
|
+
root component. It picks the startup language for you —
|
|
162
|
+
**persisted choice → browser preference → `defaultLang`** — and loads it. Gate
|
|
163
|
+
your UI on the `isReady` signal to avoid a flash of untranslated content:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { Component, inject } from '@angular/core';
|
|
167
|
+
import { TranslateStore } from '@ng-linguo/linguo';
|
|
168
|
+
|
|
169
|
+
@Component({
|
|
170
|
+
selector: 'app-root',
|
|
171
|
+
template: `@if (store.isReady()) {
|
|
172
|
+
<router-outlet />
|
|
173
|
+
} @else {
|
|
174
|
+
<app-splash />
|
|
175
|
+
}`,
|
|
176
|
+
})
|
|
177
|
+
export class App {
|
|
178
|
+
protected readonly store = inject(TranslateStore);
|
|
179
|
+
constructor() {
|
|
180
|
+
void this.store.restoreLang(); // resolve + load the startup language
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The active language is saved to `localStorage` (key `ng-linguo.lang`), and the
|
|
186
|
+
browser's preferred language is used on the first visit — both **on by default**
|
|
187
|
+
and **SSR-safe** (no-ops on the server). Set `supportedLangs` so a stored or
|
|
188
|
+
browser value can be matched to a language you actually ship. To switch language
|
|
189
|
+
later, call `store.setLang('pl')` (which also saves the choice). The full set of
|
|
190
|
+
options — `persistSelectedLanguage`, `restoreSelectedLanguage`, `persistKey`,
|
|
191
|
+
`detectBrowserLanguage` — is in [Configuration](#configuration).
|
|
192
|
+
|
|
193
|
+
### 3. Translate
|
|
194
|
+
|
|
195
|
+
In templates use the `t` pipe or the `[t]` directive; in TypeScript use
|
|
196
|
+
`injectTranslate()`.
|
|
197
|
+
|
|
198
|
+
```html
|
|
199
|
+
<!-- plain text -->
|
|
200
|
+
{{ 'Save' | t }}
|
|
201
|
+
|
|
202
|
+
<!-- ICU placeholders & plurals -->
|
|
203
|
+
{{ 'Hello {$name}!' | t: { params: { name } } }}
|
|
204
|
+
|
|
205
|
+
<!-- context: same text, different translation -->
|
|
206
|
+
{{ 'Play' | t: { context: 'game' } }}
|
|
207
|
+
|
|
208
|
+
<!-- rich text: [tag] placeholders bound to your own templates (see the hero above) -->
|
|
209
|
+
<p t="[b]Warning:[/b] this cannot be undone">
|
|
210
|
+
<ng-template tFor="b" let-text><strong>{{ text }}</strong></ng-template>
|
|
211
|
+
</p>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { injectTranslate } from '@ng-linguo/linguo';
|
|
216
|
+
|
|
217
|
+
const t = injectTranslate();
|
|
218
|
+
|
|
219
|
+
// Reactive and efficient: recomputes only when `name()` or the active language
|
|
220
|
+
// changes. Prefer this for frequently-updated or looped bindings.
|
|
221
|
+
readonly greeting = computed(() => t('Hello {$name}!', { params: { name: this.name() } }));
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Until you generate translation files (step 4), every string falls through to the
|
|
225
|
+
English you wrote, so the app is fully usable from the first line of code.
|
|
226
|
+
|
|
227
|
+
### 4. Generate the translation files
|
|
228
|
+
|
|
229
|
+
The strings above are also your source catalog. Use the
|
|
230
|
+
[`@ng-linguo/extract`](#translation-workflow) CLI to collect them and produce the
|
|
231
|
+
JSON your loader serves:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
npm i -D @ng-linguo/extract # one-time: install the CLI
|
|
235
|
+
npx linguo-extract init --locales en,pl,de # create linguo.config.json
|
|
236
|
+
npx linguo-extract extract # scan source → en/pl/de .po catalogs
|
|
237
|
+
npx linguo-extract translate --all # fill missing entries with AI (optional)
|
|
238
|
+
npx linguo-extract compile # .po → runtime JSON
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
That's the whole loop. See [Translation workflow](#translation-workflow) for the
|
|
242
|
+
interactive menu, adding languages, and translating by hand.
|
|
243
|
+
|
|
244
|
+
### Which API should I use?
|
|
245
|
+
|
|
246
|
+
All three resolve the same translations; they differ in where they run and what
|
|
247
|
+
they can render.
|
|
248
|
+
|
|
249
|
+
| Use… | When | Notes |
|
|
250
|
+
| ------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
251
|
+
| `[t]` **directive** | Translating an element, **rich text with slots**, or hot lists | The most efficient option for the DOM. Re-renders via an `effect()` only when something changes. |
|
|
252
|
+
| `injectTranslate()` | TypeScript, or a binding read inside a `computed()` | Zero work per change-detection pass. Best for frequently-updated or looped bindings. Slots → text. |
|
|
253
|
+
| `t` **pipe** | Quick inline strings, attribute bindings | Convenient, but it's an **impure** pipe (see below). Slots degrade to plain text. |
|
|
254
|
+
|
|
255
|
+
> **A note on the `t` pipe.** Angular re-evaluates an impure pipe on _every_
|
|
256
|
+
> change-detection pass. The `t` pipe has to be impure to react to a language
|
|
257
|
+
> switch (a pure pipe only re-runs when its input reference changes), so it
|
|
258
|
+
> memoizes aggressively to stay cheap. That's perfectly fine for ordinary
|
|
259
|
+
> templates — but in a long `@for` list or a hot binding, prefer the `[t]`
|
|
260
|
+
> directive or `injectTranslate()` + `computed()`, which do no per-pass work.
|
|
261
|
+
> The directive is also the only option that renders slot tags as real DOM; the
|
|
262
|
+
> pipe and `injectTranslate()` return a string, so slots collapse to their text.
|
|
263
|
+
|
|
264
|
+
### Performance
|
|
265
|
+
|
|
266
|
+
- **The `t` pipe is memoized.** It only re-translates when the key, `params`,
|
|
267
|
+
`context`, or language actually change — so passing a fresh `{ params: … }`
|
|
268
|
+
object on every render is just a quick equality check, not a re-format.
|
|
269
|
+
- **`injectTranslate()` + `computed()` does no per-pass work** — it recomputes
|
|
270
|
+
only when its signal inputs change. Reach for it on hot paths.
|
|
271
|
+
- **ICU messages are compiled once and cached** per `(format, locale, message)`,
|
|
272
|
+
so repeated formatting of the same pattern is a map lookup.
|
|
273
|
+
|
|
274
|
+
## Translation workflow
|
|
275
|
+
|
|
276
|
+
`@ng-linguo/extract` is a pure-Node CLI (no Angular dependency, so it never
|
|
277
|
+
drags the framework into your tooling) that turns your source into translation
|
|
278
|
+
files and back. It reads a `linguo.config.json` — auto-discovered — listing your
|
|
279
|
+
locales and paths. Install it once as a dev dependency; its bin is
|
|
280
|
+
`linguo-extract`, so you invoke it with `npx` (or from an npm script):
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
npm i -D @ng-linguo/extract # install once; the bin is `linguo-extract`
|
|
284
|
+
npx linguo-extract init # create/edit linguo.config.json
|
|
285
|
+
npx linguo-extract extract # scan source → <locale>.po catalogs (additive)
|
|
286
|
+
npx linguo-extract translate # fill missing entries with AI (needs a translator)
|
|
287
|
+
npx linguo-extract compile # .po catalogs → runtime <locale>.json
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### The interactive menu
|
|
291
|
+
|
|
292
|
+
New to the tool? Run it with **no command** to open a guided menu that walks
|
|
293
|
+
through every step — extract, compile, translate, run the full pipeline — and
|
|
294
|
+
includes a BIOS-style settings editor where each config field carries an
|
|
295
|
+
inline description:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
npx linguo-extract # guided menu (also creates/edits the config)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Everything the menu does is also a flag-driven command, so you can graduate to
|
|
302
|
+
scripts whenever you like.
|
|
303
|
+
|
|
304
|
+
### Extraction is additive
|
|
305
|
+
|
|
306
|
+
`extract` scans your `.ts` and `.html` for the `t` pipe, the `[t]` directive,
|
|
307
|
+
`injectTranslate()` calls, and `mark()`, then **merges** the results into your
|
|
308
|
+
existing `.po` catalogs. New strings are added, removed ones are dropped, and
|
|
309
|
+
**every translation you already have is preserved** — entries are matched by
|
|
310
|
+
their source text plus `context`. Re-running it is safe and idempotent.
|
|
311
|
+
|
|
312
|
+
(Need to keep a documentation sample or fixture out of the scan? Wrap it in
|
|
313
|
+
`linguo-ignore-start` / `linguo-ignore-end` comments.)
|
|
314
|
+
|
|
315
|
+
### Adding a language
|
|
316
|
+
|
|
317
|
+
Adding a locale is a couple of steps — or a couple of clicks in the menu:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
npx linguo-extract init --locales en,pl,de,fr # add `fr` to the config
|
|
321
|
+
npx linguo-extract extract # seeds fr.po with the source strings
|
|
322
|
+
npx linguo-extract translate --locale fr # fill it in with AI…
|
|
323
|
+
# …or: npx linguo-extract copyprompt fr # …or copy a prompt for any chat model
|
|
324
|
+
npx linguo-extract compile # produce fr.json
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Translating with AI
|
|
328
|
+
|
|
329
|
+
Because the source strings are full English sentences (not opaque keys), an LLM
|
|
330
|
+
has all the context it needs. ng-linguo writes a self-contained prompt that
|
|
331
|
+
teaches the model your `context` notes, slot tags, and plural rules, and only
|
|
332
|
+
ever sends entries that are still missing. Two ways to run it:
|
|
333
|
+
|
|
334
|
+
- **Clipboard (no key, no config):** `npx linguo-extract copyprompt pl` copies
|
|
335
|
+
the prompt; paste it into any chat model and save the reply over `pl.po`.
|
|
336
|
+
- **Automatic:** point the `translator` config field at a small module that
|
|
337
|
+
calls your AI provider. ng-linguo builds the prompt and merges the reply; your
|
|
338
|
+
SDK and API key stay yours. See the
|
|
339
|
+
[`@ng-linguo/extract` README](https://github.com/jmwierzbicki/linguo/tree/main/packages/extract#readme)
|
|
340
|
+
for a copy-paste module (OpenAI, Anthropic, or any provider).
|
|
341
|
+
|
|
342
|
+
### In CI
|
|
343
|
+
|
|
344
|
+
Every command runs non-interactively and deterministically, so the pipeline
|
|
345
|
+
drops into CI as-is:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
npx linguo-extract extract # fails the build if it errors; idempotent otherwise
|
|
349
|
+
npx linguo-extract translate --all # optional: fill any gaps (needs a translator)
|
|
350
|
+
npx linguo-extract compile
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
`init` is scriptable too: `npx linguo-extract init --locales en,pl,de --out public/i18n`.
|
|
354
|
+
|
|
355
|
+
## Configuration
|
|
356
|
+
|
|
357
|
+
ng-linguo has two independent configs that don't overlap by accident: this
|
|
358
|
+
**runtime** config (passed to `provideTranslate`, shipped in your browser
|
|
359
|
+
bundle) and the **build-time** [`linguo.config.json`](#translation-workflow)
|
|
360
|
+
(read only by the Node CLI). The runtime never reads the CLI's file. The one
|
|
361
|
+
thing both name is the locale list — `supportedLangs` here vs. `locales` there —
|
|
362
|
+
and `supportedLangs` is optional: it exists purely to match a saved or
|
|
363
|
+
browser-preferred language to one you actually ship.
|
|
364
|
+
|
|
365
|
+
### `provideTranslate(options)`
|
|
366
|
+
|
|
367
|
+
| Option | Type | Default | What it does |
|
|
368
|
+
| ------------------------- | ---------------------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
369
|
+
| `defaultLang` | `string` | _(required)_ | The language reported before anything loads, and the guaranteed fallback. |
|
|
370
|
+
| `loader` | `TranslationLoader \| () => TranslationLoader` | _(required)_ | How translations are fetched. The factory form runs in DI, so the loader can inject services (e.g. `HttpClient`). |
|
|
371
|
+
| `supportedLangs` | `string[]` | _(none)_ | Languages you ship. Used to match a persisted/browser value; browser detection is skipped when omitted. |
|
|
372
|
+
| `persistSelectedLanguage` | `boolean` | `true` | Save the active language to `localStorage` when it changes. SSR-safe. |
|
|
373
|
+
| `restoreSelectedLanguage` | `boolean` | = `persistSelectedLanguage` | Read the saved language back on startup (inside `restoreLang()`). SSR-safe. |
|
|
374
|
+
| `persistKey` | `string` | `'ng-linguo.lang'` | The `localStorage` key used for the saved language. |
|
|
375
|
+
| `detectBrowserLanguage` | `boolean` | `true` | On first run, match `navigator.languages` against `supportedLangs`. SSR-safe. |
|
|
376
|
+
|
|
377
|
+
`provideIcu({ defaultFormat })` accepts `'mf2'` (default) or `'mf1'`.
|
|
378
|
+
`createHttpLoader({ prefix, suffix })` defaults to `/assets/i18n/` + `.json`,
|
|
379
|
+
fetching `${prefix}${lang}${suffix}`. A loader is just an object with a
|
|
380
|
+
`load(lang): Promise<Record<string, string>>` method, so any source works.
|
|
381
|
+
|
|
382
|
+
## Packages & entry points
|
|
383
|
+
|
|
384
|
+
| Import | What it gives you |
|
|
385
|
+
| -------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
386
|
+
| `@ng-linguo/linguo` | `TranslateStore`, `provideTranslate`, the `t` pipe, the `[t]` directive, `injectTranslate`, `mark` |
|
|
387
|
+
| `@ng-linguo/linguo/icu` | `provideIcu` — ICU MessageFormat 1 + 2 |
|
|
388
|
+
| `@ng-linguo/linguo/http` | `createHttpLoader` — `HttpClient`-backed loader |
|
|
389
|
+
| `@ng-linguo/extract` | build-time extraction/translate/compile CLI (pure Node) |
|
|
390
|
+
| `@ng-linguo/eslint-plugin` | lint config so the a11y linter trusts empty `[t]` elements |
|
|
391
|
+
|
|
392
|
+
## Contributing
|
|
393
|
+
|
|
394
|
+
This is an Nx + pnpm monorepo.
|
|
395
|
+
[CLAUDE.md](https://github.com/jmwierzbicki/linguo/blob/main/CLAUDE.md) is the
|
|
396
|
+
source of truth for architecture, code style, testing, and release conventions —
|
|
397
|
+
read it first.
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
pnpm install
|
|
401
|
+
pnpm nx run-many -t lint test build # the full suite (what CI runs)
|
|
402
|
+
pnpm nx serve playground # the demo app
|
|
17
403
|
```
|
|
18
404
|
|
|
19
|
-
|
|
20
|
-
`[t]` directive, `injectTranslate`, slot tags.
|
|
21
|
-
- **`@ng-linguo/linguo/icu`** — ICU MessageFormat (MF1 + MF2) via `provideIcu`.
|
|
22
|
-
- **`@ng-linguo/linguo/http`** — an HTTP `TranslationLoader` (`createHttpLoader`).
|
|
405
|
+
## License
|
|
23
406
|
|
|
24
|
-
|
|
25
|
-
package (pure Node, no Angular dependency).
|
|
407
|
+
[MIT](https://github.com/jmwierzbicki/linguo/blob/main/LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-linguo/linguo",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "A modern, signal-first i18n runtime for Angular 18+ — built on SignalStore, with a translator-safe slot syntax, ICU, and tree-shakeable HTTP loading.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "jmwierzbicki",
|