@juit/vue-i18n 0.4.0 → 1.0.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/AGENTS.md ADDED
@@ -0,0 +1,187 @@
1
+ # Using and maintaining @juit/vue-i18n
2
+
3
+ This is a small Vue 3 internationalization library built around native `Intl`
4
+ APIs. Keep it small: full locale negotiation, CLDR plural rules, and a richer
5
+ message syntax are outside its intended scope. Read `README.md` for public API
6
+ examples and `package.json` for current dependencies and commands.
7
+
8
+ ## Guidance for consumer applications
9
+
10
+ Use this section when helping an application adopt the package. The source map
11
+ and verification commands below apply to maintaining this repository; use the
12
+ consumer application's own build and test commands when working there.
13
+
14
+ ### Install and configure
15
+
16
+ - Install with `npm install @juit/vue-i18n` and use a compatible Vue 3 version
17
+ (see `peerDependencies` in `package.json`). The package exports ESM and relies
18
+ on native `Intl` APIs, including `Intl.supportedValuesOf`; it provides no
19
+ polyfills.
20
+ - Import runtime APIs and types from `@juit/vue-i18n`. Do not import internal
21
+ `lib/` files or use `makeTranslator` in consumer code.
22
+ - Register `app.use(i18n, options)` before mounting the app. `defaultLanguage`
23
+ is required; provide a translation in its base language for each message.
24
+ - Language selection is explicit. For browser preferences, use
25
+ `new LanguageMatcher(['en', 'de']).match(navigator.languages)` and pass the
26
+ result as `defaultLanguage`. Access `navigator` only in browser code; on the
27
+ server, pass preferences obtained from the request or a configured default.
28
+
29
+ Prefer a configuration module that exports the actual runtime options and
30
+ derives key types from its message dictionary:
31
+
32
+ ```ts
33
+ import type { I18nOptions, Translations } from '@juit/vue-i18n'
34
+
35
+ const translations = {
36
+ hello: { en: 'Hello, {name}!', de: 'Hallo, {name}!' },
37
+ cats: { en: ['one cat', '{n} cats'], de: ['eine Katze', '{n} Katzen'] },
38
+ } as const satisfies Translations
39
+
40
+ declare module '@juit/vue-i18n' {
41
+ interface I18nConfiguration {
42
+ languages: 'en' | 'de',
43
+ translationKeys: keyof typeof translations,
44
+ }
45
+ }
46
+
47
+ export const i18nOptions = {
48
+ defaultLanguage: 'en',
49
+ translations,
50
+ } satisfies I18nOptions
51
+ ```
52
+
53
+ Import `i18nOptions` in the app entry point and pass it to `app.use(i18n,
54
+ i18nOptions)`. Keep the augmentation in a module included by the application's
55
+ TypeScript configuration. Declaration merging supplies compile-time checks;
56
+ it does not register translations or formats at runtime. For custom formats,
57
+ use `satisfies DateTimeFormats` / `satisfies NumberFormats`, derive the
58
+ corresponding configuration unions with `keyof typeof`, and pass those same
59
+ dictionaries to the plugin. See [Configuring Types](README.md#configuring-types).
60
+
61
+ ### Use the translator
62
+
63
+ - Call `useTranslator()` inside component setup or another valid Vue injection
64
+ context. For ordinary helper functions, pass the translator as an argument
65
+ instead of calling the composable at module scope.
66
+ - Use `translator.t('hello', { name: 'Alice' })`, `translator.tc('cats', count)`,
67
+ `translator.n(amount, 'EUR')`, and `translator.d(date, 'shortDate')`.
68
+ Templates expose the same methods as `$t`, `$tc`, `$n`, and `$d`.
69
+ - Keep translated text in templates or a `computed(() => translator.t(...))`
70
+ when it must follow locale changes. A string translated once during setup
71
+ does not update itself. Read `translator.language` / `region` / `locale`
72
+ through the reactive object rather than destructuring their current values.
73
+ - Change locale through those properties. Only language and region are
74
+ supported; the message syntax and plural rules below are intentionally basic.
75
+ - Prefer tuples when messages contain literal pipes. Use `as const satisfies
76
+ Translations` to retain tuple and key inference. Parameters accept strings
77
+ and numbers; translations produce plain strings, not Vue components.
78
+ - For asynchronously loaded messages, use `utils.updateTranslations(...)`
79
+ rather than mutating the original dictionary. Updates are not reactive:
80
+ coordinate loading with application state so consumers evaluate translations
81
+ again after loading completes.
82
+ - For server rendering, initialize a translator per app/request. Choose
83
+ matching initial locales and explicit time zones on server and client when
84
+ formatted output must agree.
85
+
86
+ ## Source map
87
+
88
+ - `lib/index.ts`: public exports, configuration types, Vue plugin installation,
89
+ `useTranslator()`, component global types, and `LanguageMatcher`.
90
+ - `lib/translator.ts`: reactive locale state, translation parsing and caching,
91
+ number/date formatting, and translation updates. `makeTranslator` is an
92
+ internal factory; it is not a runtime export of the package entry point.
93
+ - `lib/iso-639.ts`, `lib/iso-3166.ts`, `lib/iso-4217.ts`: reference types,
94
+ frozen code arrays, and type guards.
95
+ - `test/01-i18n.test.ts`: translator and reference-data runtime tests.
96
+ - `test/02-app.test.ts` and `test/test.vue`: Vue integration tests.
97
+ - `test/03-matcher.test.ts`: matcher runtime and type assertions.
98
+ - `test/types/`: isolated compiler-only tests for default and augmented types.
99
+ - `test/data/`: authoritative fixtures for this repository's country and
100
+ language tables. See its README for provenance.
101
+
102
+ ## Intentional behavior
103
+
104
+ ### Locales and matching
105
+
106
+ - Translators retain only language and region, including on construction and
107
+ assignment to `locale`. Scripts and Unicode extensions are discarded.
108
+ - Setting `language` preserves the region; setting `region` preserves the
109
+ language. Setting the region to `undefined` removes it.
110
+ - Translation lookup tries the current regional variant, current base language,
111
+ configured default regional variant, and default base language, where present.
112
+ - `Intl.Locale` canonicalizes some ISO language codes to other codes. The
113
+ resulting mismatch for a few languages is an accepted limitation; do not add
114
+ an alias table merely to eliminate it.
115
+ - `LanguageMatcher` is independent of Vue. It normalizes case, strips suffixes
116
+ after `-` or `_`, and chooses the first supported input preference. Its fallback
117
+ is the first configured language, not the first ISO language alphabetically.
118
+ - Matcher inputs accept readonly tuples/arrays. Its non-empty generic tuple
119
+ `T` is preserved as `Readonly<T>` in `availableLanguages`; `defaultLanguage`
120
+ is `T[0]`, and `match()` returns `T[number]`. Construction copies the input.
121
+
122
+ ### Messages and updates
123
+
124
+ - A message is a string or a readonly tuple of one to three strings. One variant
125
+ serves all counts; two mean singular/plural (zero uses plural); three mean
126
+ zero/singular/plural. Only counts zero and one receive special treatment.
127
+ - `t()` delegates to `tc()` with count one. `params.n` overrides both the value
128
+ inserted for `{n}` and the count used for variant selection.
129
+ - Strings split at unescaped pipes. Tuples skip pipe splitting and pipe
130
+ unescaping entirely. Tuple inputs are copied, including during updates.
131
+ - Trailing undefined tuple slots are omitted; gaps and invalid tuple lengths
132
+ are rejected. Empty strings fall through during lookup and are ignored by
133
+ updates. Use `['']` for an intentionally blank translation.
134
+ - Placeholders are case-sensitive, trim their names, and support balanced nested
135
+ braces. Missing parameters render as `{name}`. Only own parameter properties
136
+ are substituted; inserted values are never parsed again.
137
+ - Immediately before a pipe or placeholder, each backslash pair produces one
138
+ literal backslash and an odd remainder escapes the pipe or placeholder.
139
+ Tuples apply only placeholder escaping. Final rendered output is trimmed.
140
+ - Parsed templates contain literal strings and `{ param: string }` tokens.
141
+ Cache them independently of parameter values. Caches are scoped to the
142
+ translator's translation map, current language/region, and message key.
143
+ - Updates are validated and copied before stored messages are changed. They
144
+ invalidate parsed caches but intentionally do not trigger Vue reactivity.
145
+ Locale changes are reactive.
146
+ - Preserve `Map` storage for messages and null-prototype format dictionaries:
147
+ keys such as `constructor` and `__proto__` must not resolve inherited entries.
148
+
149
+ ### Formatting and reference data
150
+
151
+ - Date time-zone precedence is explicit argument, format option, configured
152
+ default, then runtime default. Do not mutate the caller's options.
153
+ - The date formatter uses `Object.create` with an own `timeZone` descriptor to
154
+ preserve inherited/non-enumerable options and override readonly time zones.
155
+ The changed getter receiver is an accepted edge case for private-field getters.
156
+ - Numeric interpolation uses the current locale and default number format.
157
+ - Country codes include CLDR's `XK` for Kosovo. Keep language/country reference
158
+ names aligned with `test/data/`, rather than independently modernizing them.
159
+ - Currency codes and built-in currency aliases come from the runtime's
160
+ `Intl.supportedValuesOf('currency')`. The static currency union can differ;
161
+ this is intentional. Do not replace runtime discovery with the static table.
162
+
163
+ ## Public types and verification
164
+
165
+ `I18nConfiguration` supports declaration merging for language, translation-key,
166
+ date-format, and number-format unions. With a configured subset of languages,
167
+ all base languages are required in each translation; regional entries remain
168
+ optional. Without configuration, base languages are optional and keys/aliases
169
+ accept arbitrary strings. Built-in format aliases remain available when custom
170
+ aliases are configured. Translation updates accept partial messages.
171
+
172
+ Keep configured and unconfigured type tests in separate TypeScript projects:
173
+ declaration merging affects the entire project. Use `expectTypeOf` for inferred
174
+ types and descriptive `@ts-expect-error` assertions for rejected inputs. Vitest
175
+ alone does not enforce those compiler assertions.
176
+
177
+ - `npm run check`: type-check source, runtime tests, Vue templates, and isolated
178
+ type fixtures; run Vitest with coverage; run ESLint.
179
+ - `npm test`: runtime tests and coverage only.
180
+ - `npm run build`: run all checks, then build ESM and bundled declarations into
181
+ `dist/`, with Vue externalized.
182
+
183
+ Add focused regressions for behavioral fixes and public type changes. Prefer
184
+ the full check before completing code changes; for comment-only edits, lint
185
+ and verification that code/types are unchanged are sufficient. Do not edit
186
+ generated `dist/` or `coverage/` files. Match the existing TypeScript style and
187
+ keep README examples and source comments consistent with changed behavior.
package/NOTICE.md ADDED
@@ -0,0 +1,13 @@
1
+ # Copyright 2021-2024 Juit GmbH
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ [http://www.apache.org/licenses/](http://www.apache.org/licenses/)
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.