@owlmeans/i18n 0.1.7 → 0.1.9
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 +17 -0
- package/agent-meta/instructions/i18n.instructions.md +42 -0
- package/agent-meta/instructions/localization.instructions.md +72 -0
- package/agent-meta/manifest.json +37 -0
- package/agent-meta/skills/i18n/SKILL.md +96 -0
- package/agent-meta/skills/localization/SKILL.md +108 -0
- package/package.json +1 -1
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.9"`
|
|
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.9",
|
|
5
|
+
"generatedAt": "2026-06-14T13:34:06.124Z",
|
|
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.9"` 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.
|