@owlmeans/i18n 0.1.7 → 0.1.8

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 CHANGED
@@ -53,3 +53,20 @@ The global translation store. Read by platform-specific i18n adapters (e.g. `@ow
53
53
  ## Related Packages
54
54
 
55
55
  - [`@owlmeans/client-i18n`](../client-i18n) — React i18next adapter that reads from `i18nStorage`
56
+
57
+ <!-- owlmeans:agent-guidance:start -->
58
+ ## Agent guidance
59
+
60
+ This package ships embedded Claude Code skills and GitHub Copilot instructions under
61
+ `agent-meta/`. After installing your `@owlmeans/*` packages, run the OwlMeans
62
+ agent-skills installer to place them into your project's native locations
63
+ (`.claude/skills/` and `.github/instructions/`):
64
+
65
+ ```sh
66
+ npx @owlmeans/agent-skills
67
+ ```
68
+
69
+ The embedded files are version-matched to this package release. Do not edit them
70
+ directly — they are regenerated on each publish. To contribute guidance edits,
71
+ open a PR against the source monorepo.
72
+ <!-- owlmeans:agent-guidance:end -->
@@ -0,0 +1,42 @@
1
+ ---
2
+ description: "How to use @owlmeans/i18n — the core localization registry. Use when adding translatable strings to a library package or importing from this package."
3
+ applyTo: "**/*.ts, **/*.tsx"
4
+ ---
5
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
6
+
7
+ # @owlmeans/i18n
8
+
9
+ **Layer:** Core (no runtime deps)
10
+ **Version:** `"@owlmeans/i18n": "^0.1.8"`
11
+
12
+ ## Key exports
13
+
14
+ | Export | Description |
15
+ |--------|-------------|
16
+ | `addI18nLib(lng, resource, data, opts?)` | Register library strings (ns=`'lib'`) |
17
+ | `addI18nApp(lng, resource, data, opts?)` | Register app strings (ns=resource name) |
18
+ | `initI18nResource(lng, resource, ns?)` | Drain bundles for a language (internal use by client-i18n) |
19
+ | `SUPPORTED_LNGS` | `['en','pl','ru','be','uk','es','de']` |
20
+ | `I18nTier` | `Library \| App` |
21
+ | `I18nConfig` | `{ defaultLng?, defaultNs?, fallbackLng?, supportedLngs? }` |
22
+
23
+ ## Per-package i18n registration pattern
24
+
25
+ ```typescript
26
+ // src/i18n.ts
27
+ import { addI18nLib } from '@owlmeans/i18n'
28
+ import en from './i18n/en.json' with { type: 'json' }
29
+ import pl from './i18n/pl.json' with { type: 'json' }
30
+ // … ru, be, uk, es, de
31
+ addI18nLib('en', 'my-package', en)
32
+ addI18nLib('pl', 'my-package', pl)
33
+ // … all 7 languages
34
+ ```
35
+
36
+ Export from `src/index.ts`: `export * from './i18n.js'`
37
+
38
+ ## Invariants
39
+
40
+ - **All 7 languages required**: never add a key to `en.json` without adding it to pl/ru/be/uk/es/de in the same commit.
41
+ - Library packages use `addI18nLib`; apps use `addI18nApp`.
42
+ - Custom namespace via `opts.ns` only when keys must live outside `'lib'` (e.g. DID's `{ ns: 'did' }`).
@@ -0,0 +1,72 @@
1
+ ---
2
+ description: "OwlMeans localization conventions — tiered namespace model, compound-prefix keys, 7-language requirement, override pattern. Apply whenever adding UI strings, JSON translation files, or i18n hooks to any package."
3
+ applyTo: "**/*.ts, **/*.tsx, **/i18n/**"
4
+ ---
5
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
6
+
7
+ # OwlMeans Localization Conventions
8
+
9
+ ## Canonical languages
10
+
11
+ `SUPPORTED_LNGS` from `@owlmeans/i18n`:
12
+ ```
13
+ en pl ru be uk es de
14
+ ```
15
+
16
+ **Every new translation key must exist in all 7 language files in the same commit.**
17
+
18
+ ## Tiered namespace model
19
+
20
+ | Package type | Register with | namespace | key path |
21
+ |---|---|---|---|
22
+ | `@owlmeans/*` library | `addI18nLib` | `'lib'` | `resource.prefix.key` |
23
+ | App / shared project | `addI18nApp` | resource name | `resource.prefix.key` |
24
+
25
+ App tier overrides Library tier for the same key.
26
+
27
+ ## Compound prefix keys
28
+
29
+ ```
30
+ t('title') via useI18nLib('errors', 'form')
31
+ → namespace='lib', key='errors.form.title'
32
+ → resolves errors.json → form.title
33
+
34
+ t('title') via useI18nApp(undefined, 'home-screen')
35
+ → namespace='my-app', key='my-app.home-screen.title'
36
+ → resolves my-app i18n data → home-screen.title
37
+ ```
38
+
39
+ ## Context-based prefix switching
40
+
41
+ `PanelContext` (from `@owlmeans/client-panel`) injects `{ resource, ns, prefix }` into a React subtree. Children call `usePanelI18n()` / `useFormI18n()` which inherit the context.
42
+
43
+ ```tsx
44
+ <PanelContext resource="wl-manager" ns="lib" prefix="wl-dns">
45
+ <DnsSection />
46
+ </PanelContext>
47
+ ```
48
+
49
+ ## Hook selection guide
50
+
51
+ | Where | Hook |
52
+ |---|---|
53
+ | `@owlmeans/*` library component | `useI18nLib('resource', 'prefix')` |
54
+ | App / project component | `useI18nApp(undefined, 'prefix')` |
55
+ | Inside panel/form context | `usePanelI18n('name')` / `useFormI18n()` |
56
+ | ns ≠ resource (rare) | `useI18n('resource', 'ns', 'prefix')` |
57
+
58
+ ## Adding a new translatable string — checklist
59
+
60
+ 1. Add key to `en.json`
61
+ 2. Add the same key (with translation) to `pl.json`, `ru.json`, `be.json`, `uk.json`, `es.json`, `de.json`
62
+ 3. Use a hook in the component — never hardcode the string
63
+
64
+ ## Language switcher
65
+
66
+ ```tsx
67
+ import { useLanguage } from '@owlmeans/client-i18n'
68
+ import { SUPPORTED_LNGS } from '@owlmeans/i18n'
69
+
70
+ const [lng, setLng] = useLanguage()
71
+ // persisted to localStorage automatically
72
+ ```
@@ -0,0 +1,37 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "package": "@owlmeans/i18n",
4
+ "version": "0.1.8",
5
+ "generatedAt": "2026-06-11T16:30:27.160Z",
6
+ "canonicalRepo": "https://github.com/owlmeans/common",
7
+ "entries": [
8
+ {
9
+ "kind": "skill",
10
+ "name": "localization",
11
+ "category": "multi-package",
12
+ "file": "skills/localization/SKILL.md",
13
+ "canonicalPath": ".claude/skills/localization/SKILL.md"
14
+ },
15
+ {
16
+ "kind": "skill",
17
+ "name": "i18n",
18
+ "category": "package-specific",
19
+ "file": "skills/i18n/SKILL.md",
20
+ "canonicalPath": ".claude/skills/i18n/SKILL.md"
21
+ },
22
+ {
23
+ "kind": "instruction",
24
+ "name": "localization",
25
+ "category": "multi-package",
26
+ "file": "instructions/localization.instructions.md",
27
+ "canonicalPath": ".github/instructions/localization.instructions.md"
28
+ },
29
+ {
30
+ "kind": "instruction",
31
+ "name": "i18n",
32
+ "category": "package-specific",
33
+ "file": "instructions/i18n.instructions.md",
34
+ "canonicalPath": ".github/instructions/i18n.instructions.md"
35
+ }
36
+ ]
37
+ }
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: i18n
3
+ description: How to use @owlmeans/i18n — the core localization registry (no runtime deps). Auto-invoked when adding translatable strings to a library package, importing from this package, or working with the tier/priority system.
4
+ user-invocable: false
5
+ ---
6
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
7
+
8
+ # @owlmeans/i18n
9
+
10
+ **Layer:** Core (no runtime deps)
11
+ **Install:** `"@owlmeans/i18n": "^0.1.8"` in `dependencies`
12
+
13
+ ## Purpose
14
+
15
+ Global registration store that packages write into at import time. React clients drain it lazily via `@owlmeans/client-i18n`. The store is addressed by **(ns, resource, language)**.
16
+
17
+ ## Key Exports
18
+
19
+ | Export | Description |
20
+ |--------|-------------|
21
+ | `addI18nLib(lng, resource, data, opts?)` | Register library-owned strings (ns defaults to `'lib'`) |
22
+ | `addI18nApp(lng, resource, data, opts?)` | Register app-owned strings (ns defaults to resource name) |
23
+ | `initI18nResource(lng, resource, ns?)` | Drain a registered bundle for a language (called by client-i18n) |
24
+ | `SUPPORTED_LNGS` | `['en','pl','ru','be','uk','es','de']` — the canonical language set |
25
+ | `DEFAULT_LNG` | `'en'` |
26
+ | `LIB_NAMESPACE` | `'lib'` |
27
+ | `I18nTier` | `Library \| App` — enum used internally |
28
+ | `I18nConfig` | `{ defaultLng?, defaultNs?, fallbackLng?, supportedLngs? }` |
29
+
30
+ ## Tiers
31
+
32
+ | Tier | Helper | Default ns | When to use |
33
+ |------|--------|-----------|-------------|
34
+ | Library | `addI18nLib` | `'lib'` | Any `@owlmeans/*` package |
35
+ | App | `addI18nApp` | resource name | Project-specific app / shared project package |
36
+
37
+ App-tier strings deep-merge **over** Library-tier strings for the same keys at resolution time — this is the override mechanism.
38
+
39
+ ## Per-package pattern
40
+
41
+ Every package that ships translatable strings exports a side-effect `i18n.ts`:
42
+
43
+ ```typescript
44
+ // src/i18n.ts
45
+ import { addI18nLib } from '@owlmeans/i18n'
46
+ import en from './i18n/en.json' with { type: 'json' }
47
+ import pl from './i18n/pl.json' with { type: 'json' }
48
+ import ru from './i18n/ru.json' with { type: 'json' }
49
+ import be from './i18n/be.json' with { type: 'json' }
50
+ import uk from './i18n/uk.json' with { type: 'json' }
51
+ import es from './i18n/es.json' with { type: 'json' }
52
+ import de from './i18n/de.json' with { type: 'json' }
53
+
54
+ addI18nLib('en', 'my-package', en)
55
+ addI18nLib('pl', 'my-package', pl)
56
+ addI18nLib('ru', 'my-package', ru)
57
+ addI18nLib('be', 'my-package', be)
58
+ addI18nLib('uk', 'my-package', uk)
59
+ addI18nLib('es', 'my-package', es)
60
+ addI18nLib('de', 'my-package', de)
61
+ ```
62
+
63
+ Then re-export from `src/index.ts`:
64
+ ```typescript
65
+ export * from './i18n.js'
66
+ ```
67
+
68
+ ## Key structure
69
+
70
+ Keys are plain dot-paths inside a JSON file:
71
+ ```json
72
+ {
73
+ "mySection": {
74
+ "title": "Title",
75
+ "description": "Description"
76
+ },
77
+ "form-field": "Invalid field"
78
+ }
79
+ ```
80
+
81
+ Consumers use `useI18nLib('my-package', 'mySection')` → `t('title')` → resolves `lib:my-package.mySection.title`.
82
+
83
+ ## Custom namespace (rare)
84
+
85
+ Use the optional `opts.ns` when keys must live in a namespace other than `'lib'`:
86
+ ```typescript
87
+ addI18nLib('en', 'wallet', walletEn, { ns: 'did' })
88
+ ```
89
+
90
+ ## Languages
91
+
92
+ All packages **must** ship all 7 languages from `SUPPORTED_LNGS`. Adding a new key → add it to all 7 files in the same commit.
93
+
94
+ ## Depends On
95
+
96
+ Nothing at runtime — pure types and helpers.
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: localization
3
+ description: OwlMeans localization conventions — tiered namespace model, compound-prefix keys, 7-language requirement, override pattern, and language switcher. Auto-invoked when adding UI strings, translation keys, language files, or working with i18n in any package.
4
+ ---
5
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
6
+
7
+ # OwlMeans Localization Conventions
8
+
9
+ ## Canonical language set
10
+
11
+ ```typescript
12
+ import { SUPPORTED_LNGS } from '@owlmeans/i18n'
13
+ // ['en', 'pl', 'ru', 'be', 'uk', 'es', 'de']
14
+ ```
15
+
16
+ **Rule: every new key must be present in all 7 language files in the same commit.** No English-only additions.
17
+
18
+ ## Addressing model (tiered shared namespace)
19
+
20
+ Keys are addressed as: `(namespace, resource.prefix.key)`.
21
+
22
+ | Who registers | Helper | namespace | key path |
23
+ |---|---|---|---|
24
+ | `@owlmeans/*` library package | `addI18nLib` | `'lib'` | `resource.prefix.key` |
25
+ | App or shared project package | `addI18nApp` | resource name | `resource.prefix.key` |
26
+
27
+ The **resource name** is a stable identifier for the registering package, e.g. `'errors'`, `'payment'`, `'viable-manager-web'`.
28
+
29
+ ## Compound prefix keys
30
+
31
+ A key like `t('title')` resolves to `namespace : resource.prefix.key`. The prefix is composed by joining segments with `.` via `composePrefix` (never by hand-concatenating strings).
32
+
33
+ Example chain:
34
+ ```
35
+ addI18nApp('en', 'viable-manager-web', data)
36
+ // data = { "home-screen": { "title": "Welcome" } }
37
+
38
+ const t = useI18nApp(undefined, 'home-screen')
39
+ // → namespace: 'viable-manager-web', keyPrefix: 'viable-manager-web.home-screen'
40
+
41
+ t('title')
42
+ // → 'viable-manager-web' : 'viable-manager-web.home-screen.title' → "Welcome"
43
+ ```
44
+
45
+ ## Context-based prefix switching
46
+
47
+ Use `PanelContext` (from `@owlmeans/client-panel`) to inject `resource`, `ns`, or `prefix` into a React sub-tree. Child components that call `usePanelI18n()` or `useFormI18n()` automatically pick up the context value.
48
+
49
+ ```tsx
50
+ // Override the active namespace/resource for a sub-tree
51
+ <PanelContext resource="wl-manager" ns="lib" prefix="wl-dns">
52
+ <DnsForm /> {/* DnsForm calls usePanelI18n() — gets ns='lib', resource='wl-manager', prefix='wl-dns' */}
53
+ </PanelContext>
54
+ ```
55
+
56
+ Use `i18n` props on individual components to override just one level:
57
+ ```tsx
58
+ <StatusLabel i18n={{ prefix: 'my-status' }} />
59
+ ```
60
+
61
+ ## App-level override of library strings
62
+
63
+ App-tier registrations deep-merge over Library-tier for the same `(ns, resource)`:
64
+ ```typescript
65
+ import '@owlmeans/error' // Library tier: lib:errors.*
66
+ import { addI18nApp } from '@owlmeans/i18n'
67
+ import myErrors from './i18n/en.json'
68
+
69
+ addI18nApp('en', 'errors', myErrors) // App tier wins for matching keys
70
+ ```
71
+
72
+ ## Per-package checklist
73
+
74
+ When adding translatable strings to any package:
75
+
76
+ 1. Create `src/i18n/en.json` (and pl, ru, be, uk, es, de in the same commit)
77
+ 2. Create `src/i18n.ts` registering all 7 languages (see `i18n` skill for the template)
78
+ 3. Re-export from `src/index.ts`: `export * from './i18n.js'`
79
+ 4. In components, use `useI18nLib` (library package) or `useI18nApp` (app package) — never hardcode UI strings
80
+
81
+ ## Language switcher
82
+
83
+ Use `useLanguage()` from `@owlmeans/client-i18n`. The selection is persisted to `localStorage`.
84
+
85
+ ```tsx
86
+ import { useLanguage } from '@owlmeans/client-i18n'
87
+ import { SUPPORTED_LNGS } from '@owlmeans/i18n'
88
+
89
+ function LanguageSwitcher() {
90
+ const [lng, setLng] = useLanguage()
91
+ return (
92
+ <select value={lng} onChange={e => setLng(e.target.value)}>
93
+ {SUPPORTED_LNGS.map(l => <option key={l} value={l}>{l.toUpperCase()}</option>)}
94
+ </select>
95
+ )
96
+ }
97
+ ```
98
+
99
+ ## When to use which hook
100
+
101
+ | Situation | Hook |
102
+ |---|---|
103
+ | React component in a `@owlmeans/*` library package | `useI18nLib('resource-name', 'prefix')` |
104
+ | React component in an app or shared project package | `useI18nApp(undefined, 'prefix')` |
105
+ | Inside `usePanelI18n` / `useFormI18n` territory (panel/form context) | `usePanelI18n('name')` / `useFormI18n()` |
106
+ | Explicit ns ≠ resource (rare, e.g. DID namespace) | `useI18n('resource', 'ns', 'prefix')` |
107
+
108
+ Never call `useI18n` directly from product code unless ns and resource genuinely differ.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/i18n",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {