@pyreon/i18n 0.3.0 → 0.7.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/src/index.ts CHANGED
@@ -1,18 +1,16 @@
1
+ export type { I18nProviderProps } from './context'
2
+ export { I18nContext, I18nProvider, useI18n } from './context'
1
3
  export { createI18n } from './create-i18n'
2
4
  export { interpolate } from './interpolation'
3
5
  export { resolvePluralCategory } from './pluralization'
4
- export { I18nProvider, useI18n, I18nContext } from './context'
5
- export { Trans, parseRichText } from './trans'
6
-
6
+ export type { TransProps } from './trans'
7
+ export { parseRichText, Trans } from './trans'
7
8
  export type {
8
9
  I18nInstance,
9
10
  I18nOptions,
10
- TranslationDictionary,
11
- TranslationMessages,
12
- NamespaceLoader,
13
11
  InterpolationValues,
12
+ NamespaceLoader,
14
13
  PluralRules,
14
+ TranslationDictionary,
15
+ TranslationMessages,
15
16
  } from './types'
16
-
17
- export type { I18nProviderProps } from './context'
18
- export type { TransProps } from './trans'
@@ -1,12 +1,12 @@
1
1
  import { createI18n } from '../create-i18n'
2
2
  import {
3
- registerI18n,
4
- unregisterI18n,
3
+ _resetDevtools,
5
4
  getActiveI18nInstances,
6
5
  getI18nInstance,
7
6
  getI18nSnapshot,
8
7
  onI18nChange,
9
- _resetDevtools,
8
+ registerI18n,
9
+ unregisterI18n,
10
10
  } from '../devtools'
11
11
 
12
12
  afterEach(() => _resetDevtools())
@@ -1,8 +1,7 @@
1
- import { h } from '@pyreon/core'
2
- import { mount } from '@pyreon/runtime-dom'
3
1
  import { effect } from '@pyreon/reactivity'
4
- import { createI18n } from '../create-i18n'
2
+ import { mount } from '@pyreon/runtime-dom'
5
3
  import { I18nProvider, useI18n } from '../context'
4
+ import { createI18n } from '../create-i18n'
6
5
  import { interpolate } from '../interpolation'
7
6
  import { resolvePluralCategory } from '../pluralization'
8
7
  import { parseRichText, Trans } from '../trans'
@@ -593,15 +592,14 @@ describe('I18nProvider / useI18n', () => {
593
592
  let received: ReturnType<typeof useI18n> | undefined
594
593
  const el = document.createElement('div')
595
594
  document.body.appendChild(el)
595
+ const Child = () => {
596
+ received = useI18n()
597
+ return null
598
+ }
596
599
  const unmount = mount(
597
- h(
598
- I18nProvider,
599
- { instance: i18n },
600
- h(() => {
601
- received = useI18n()
602
- return null
603
- }, null),
604
- ),
600
+ <I18nProvider instance={i18n}>
601
+ <Child />
602
+ </I18nProvider>,
605
603
  el,
606
604
  )
607
605
 
@@ -620,13 +618,12 @@ describe('I18nProvider / useI18n', () => {
620
618
  let received: ReturnType<typeof useI18n> | undefined
621
619
  const el = document.createElement('div')
622
620
  document.body.appendChild(el)
621
+ const Child = () => {
622
+ received = useI18n()
623
+ return null
624
+ }
623
625
  const unmount = mount(
624
- h(I18nProvider, { instance: i18n }, () => {
625
- return h(() => {
626
- received = useI18n()
627
- return null
628
- }, null)
629
- }),
626
+ <I18nProvider instance={i18n}>{() => <Child />}</I18nProvider>,
630
627
  el,
631
628
  )
632
629
 
@@ -641,17 +638,15 @@ describe('I18nProvider / useI18n', () => {
641
638
  const el = document.createElement('div')
642
639
  document.body.appendChild(el)
643
640
 
644
- const unmount = mount(
645
- h(() => {
646
- try {
647
- useI18n()
648
- } catch (e) {
649
- error = e as Error
650
- }
651
- return null
652
- }, null),
653
- el,
654
- )
641
+ const Child = () => {
642
+ try {
643
+ useI18n()
644
+ } catch (e) {
645
+ error = e as Error
646
+ }
647
+ return null
648
+ }
649
+ const unmount = mount(<Child />, el)
655
650
 
656
651
  expect(error).toBeDefined()
657
652
  expect(error!.message).toContain(
@@ -670,15 +665,14 @@ describe('I18nProvider / useI18n', () => {
670
665
  let received: ReturnType<typeof useI18n> | undefined
671
666
  const el = document.createElement('div')
672
667
  document.body.appendChild(el)
668
+ const Child = () => {
669
+ received = useI18n()
670
+ return null
671
+ }
673
672
  const unmount = mount(
674
- h(
675
- I18nProvider,
676
- { instance: i18n },
677
- h(() => {
678
- received = useI18n()
679
- return null
680
- }, null),
681
- ),
673
+ <I18nProvider instance={i18n}>
674
+ <Child />
675
+ </I18nProvider>,
682
676
  el,
683
677
  )
684
678
 
@@ -1,5 +1,4 @@
1
- import { h, Fragment } from '@pyreon/core'
2
- import type { VNode, Props } from '@pyreon/core'
1
+ import type { Props, VNode } from '@pyreon/core'
3
2
  import type { InterpolationValues } from './types'
4
3
 
5
4
  const TAG_RE = /<(\w+)>([^<]*)<\/\1>/g
@@ -107,5 +106,5 @@ export function Trans(props: TransProps): VNode | string {
107
106
  return component(part.children)
108
107
  })
109
108
 
110
- return h(Fragment, null, ...children)
109
+ return <>{children}</>
111
110
  }
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Signal, Computed } from '@pyreon/reactivity'
1
+ import type { Computed, Signal } from '@pyreon/reactivity'
2
2
 
3
3
  /** A nested dictionary of translation strings. */
4
4
  export type TranslationDictionary = {
@@ -1,30 +0,0 @@
1
- //#region src/devtools.d.ts
2
- /**
3
- * @pyreon/i18n devtools introspection API.
4
- * Import: `import { ... } from "@pyreon/i18n/devtools"`
5
- */
6
- /**
7
- * Register an i18n instance for devtools inspection.
8
- *
9
- * @example
10
- * const i18n = createI18n({ ... })
11
- * registerI18n("app", i18n)
12
- */
13
- declare function registerI18n(name: string, instance: object): void;
14
- /** Unregister an i18n instance. */
15
- declare function unregisterI18n(name: string): void;
16
- /** Get all registered i18n instance names. Cleans up garbage-collected instances. */
17
- declare function getActiveI18nInstances(): string[];
18
- /** Get an i18n instance by name (or undefined if GC'd or not registered). */
19
- declare function getI18nInstance(name: string): object | undefined;
20
- /**
21
- * Get a snapshot of an i18n instance's state.
22
- */
23
- declare function getI18nSnapshot(name: string): Record<string, unknown> | undefined;
24
- /** Subscribe to i18n registry changes. Returns unsubscribe function. */
25
- declare function onI18nChange(listener: () => void): () => void;
26
- /** @internal — reset devtools registry (for tests). */
27
- declare function _resetDevtools(): void;
28
- //#endregion
29
- export { _resetDevtools, getActiveI18nInstances, getI18nInstance, getI18nSnapshot, onI18nChange, registerI18n, unregisterI18n };
30
- //# sourceMappingURL=devtools2.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"devtools2.d.ts","names":[],"sources":["../../src/devtools.ts"],"mappings":";;AAmBA;;;;;AAMA;;;;;iBANgB,YAAA,CAAa,IAAA,UAAc,QAAA;;iBAM3B,cAAA,CAAe,IAAA;;iBAMf,sBAAA,CAAA;AAQhB;AAAA,iBAAgB,eAAA,CAAgB,IAAA;;;;iBA4BhB,eAAA,CACd,IAAA,WACC,MAAA;;iBAaa,YAAA,CAAa,QAAA;;iBAQb,cAAA,CAAA"}
@@ -1,244 +0,0 @@
1
- import { Computed, Signal } from "@pyreon/reactivity";
2
- import * as _pyreon_core0 from "@pyreon/core";
3
- import { Props, VNode, VNodeChild } from "@pyreon/core";
4
-
5
- //#region src/types.d.ts
6
- /** A nested dictionary of translation strings. */
7
- type TranslationDictionary = {
8
- [key: string]: string | TranslationDictionary;
9
- };
10
- /** Map of locale → dictionary (or namespace → dictionary). */
11
- type TranslationMessages = Record<string, TranslationDictionary>;
12
- /**
13
- * Async function that loads translations for a locale and namespace.
14
- * Return the dictionary for that namespace, or undefined if not found.
15
- */
16
- type NamespaceLoader = (locale: string, namespace: string) => Promise<TranslationDictionary | undefined>;
17
- /** Interpolation values for translation strings. */
18
- type InterpolationValues = Record<string, string | number>;
19
- /** Pluralization rules map — locale → function that picks the plural form. */
20
- type PluralRules = Record<string, (count: number) => string>;
21
- /** Options for creating an i18n instance. */
22
- interface I18nOptions {
23
- /** The initial locale (e.g. "en"). */
24
- locale: string;
25
- /** Fallback locale used when a key is missing in the active locale. */
26
- fallbackLocale?: string;
27
- /** Static messages keyed by locale. */
28
- messages?: Record<string, TranslationDictionary>;
29
- /**
30
- * Async loader for namespace-based translation loading.
31
- * Called with (locale, namespace) when `loadNamespace()` is invoked.
32
- */
33
- loader?: NamespaceLoader;
34
- /**
35
- * Default namespace used when `t()` is called without a namespace prefix.
36
- * Defaults to "common".
37
- */
38
- defaultNamespace?: string;
39
- /**
40
- * Custom plural rules per locale.
41
- * If not provided, uses `Intl.PluralRules` where available.
42
- */
43
- pluralRules?: PluralRules;
44
- /**
45
- * Missing key handler — called when a translation key is not found.
46
- * Useful for logging, reporting, or returning a custom fallback.
47
- */
48
- onMissingKey?: (locale: string, key: string, namespace?: string) => string | undefined;
49
- }
50
- /** The public i18n instance returned by `createI18n()`. */
51
- interface I18nInstance {
52
- /**
53
- * Translate a key with optional interpolation.
54
- * Reads the current locale reactively — re-evaluates in effects/computeds.
55
- *
56
- * Key format: "key" (uses default namespace) or "namespace:key".
57
- * Nested keys use dots: "user.greeting" or "auth:errors.invalid".
58
- *
59
- * Interpolation: "Hello {{name}}" + { name: "Alice" } → "Hello Alice"
60
- * Pluralization: key with "_one", "_other" etc. suffixes + { count: N }
61
- */
62
- t: (key: string, values?: InterpolationValues) => string;
63
- /** Current locale (reactive signal). */
64
- locale: Signal<string>;
65
- /**
66
- * Load a namespace's translations for the given locale (or current locale).
67
- * Returns a promise that resolves when loading is complete.
68
- */
69
- loadNamespace: (namespace: string, locale?: string) => Promise<void>;
70
- /**
71
- * Whether any namespace is currently being loaded.
72
- */
73
- isLoading: Computed<boolean>;
74
- /**
75
- * Set of namespaces that have been loaded for the current locale.
76
- */
77
- loadedNamespaces: Computed<Set<string>>;
78
- /**
79
- * Check if a translation key exists in the current locale.
80
- */
81
- exists: (key: string) => boolean;
82
- /**
83
- * Add translations for a locale (merged with existing).
84
- * Useful for adding translations at runtime without async loading.
85
- */
86
- addMessages: (locale: string, messages: TranslationDictionary, namespace?: string) => void;
87
- /**
88
- * Get all available locales (those with any registered messages).
89
- */
90
- availableLocales: Computed<string[]>;
91
- }
92
- //#endregion
93
- //#region src/create-i18n.d.ts
94
- /**
95
- * Create a reactive i18n instance.
96
- *
97
- * @example
98
- * const i18n = createI18n({
99
- * locale: 'en',
100
- * fallbackLocale: 'en',
101
- * messages: {
102
- * en: { greeting: 'Hello {{name}}!' },
103
- * de: { greeting: 'Hallo {{name}}!' },
104
- * },
105
- * })
106
- *
107
- * // Reactive translation — re-evaluates on locale change
108
- * i18n.t('greeting', { name: 'Alice' }) // "Hello Alice!"
109
- * i18n.locale.set('de')
110
- * i18n.t('greeting', { name: 'Alice' }) // "Hallo Alice!"
111
- *
112
- * @example
113
- * // Async namespace loading
114
- * const i18n = createI18n({
115
- * locale: 'en',
116
- * loader: async (locale, namespace) => {
117
- * const mod = await import(`./locales/${locale}/${namespace}.json`)
118
- * return mod.default
119
- * },
120
- * })
121
- * await i18n.loadNamespace('auth')
122
- * i18n.t('auth:errors.invalid') // looks up "errors.invalid" in "auth" namespace
123
- */
124
- declare function createI18n(options: I18nOptions): I18nInstance;
125
- //#endregion
126
- //#region src/interpolation.d.ts
127
- /**
128
- * Replace `{{key}}` placeholders in a string with values from the given record.
129
- * Supports optional whitespace inside braces: `{{ name }}` works too.
130
- * Unmatched placeholders are left as-is.
131
- */
132
- declare function interpolate(template: string, values?: InterpolationValues): string;
133
- //#endregion
134
- //#region src/pluralization.d.ts
135
- /**
136
- * Resolve the plural category for a given count and locale.
137
- *
138
- * Uses custom rules if provided, otherwise falls back to `Intl.PluralRules`.
139
- * Returns CLDR plural categories: "zero", "one", "two", "few", "many", "other".
140
- */
141
- declare function resolvePluralCategory(locale: string, count: number, customRules?: PluralRules): string;
142
- //#endregion
143
- //#region src/context.d.ts
144
- declare const I18nContext: _pyreon_core0.Context<I18nInstance | null>;
145
- interface I18nProviderProps extends Props {
146
- instance: I18nInstance;
147
- children?: VNodeChild;
148
- }
149
- /**
150
- * Provide an i18n instance to the component tree.
151
- *
152
- * @example
153
- * const i18n = createI18n({ locale: 'en', messages: { en: { hello: 'Hello' } } })
154
- *
155
- * // In JSX:
156
- * <I18nProvider instance={i18n}>
157
- * <App />
158
- * </I18nProvider>
159
- */
160
- declare function I18nProvider(props: I18nProviderProps): VNode;
161
- /**
162
- * Access the i18n instance from the nearest I18nProvider.
163
- * Must be called within a component tree wrapped by I18nProvider.
164
- *
165
- * @example
166
- * function Greeting() {
167
- * const { t, locale } = useI18n()
168
- * return <h1>{t('greeting', { name: 'World' })}</h1>
169
- * }
170
- */
171
- declare function useI18n(): I18nInstance;
172
- //#endregion
173
- //#region src/trans.d.ts
174
- interface RichPart {
175
- tag: string;
176
- children: string;
177
- }
178
- /**
179
- * Parse a translated string into an array of plain text and rich tag segments.
180
- *
181
- * @example
182
- * parseRichText("Hello <bold>world</bold>, click <link>here</link>")
183
- * // → ["Hello ", { tag: "bold", children: "world" }, ", click ", { tag: "link", children: "here" }]
184
- */
185
- declare function parseRichText(text: string): (string | RichPart)[];
186
- interface TransProps extends Props {
187
- /** Translation key (supports namespace:key syntax). */
188
- i18nKey: string;
189
- /** Interpolation values for {{placeholder}} syntax. */
190
- values?: InterpolationValues;
191
- /**
192
- * Component map for rich interpolation.
193
- * Keys match tag names in the translation string.
194
- * Values are component functions: `(children: any) => VNode`
195
- *
196
- * @example
197
- * // Translation: "Read the <terms>terms</terms> and <privacy>policy</privacy>"
198
- * components={{
199
- * terms: (children) => <a href="/terms">{children}</a>,
200
- * privacy: (children) => <a href="/privacy">{children}</a>,
201
- * }}
202
- */
203
- components?: Record<string, (children: any) => any>;
204
- /**
205
- * The i18n instance's `t` function.
206
- * Can be obtained from `useI18n()` or passed directly.
207
- */
208
- t: (key: string, values?: InterpolationValues) => string;
209
- }
210
- /**
211
- * Rich JSX interpolation component for translations.
212
- *
213
- * Allows embedding JSX components within translated strings using XML-like tags.
214
- * The `t` function resolves the translation and interpolates `{{values}}` first,
215
- * then `<tag>content</tag>` patterns are mapped to the provided components.
216
- *
217
- * @example
218
- * // Translation: "You have <bold>{{count}}</bold> unread messages"
219
- * const { t } = useI18n()
220
- * <Trans
221
- * t={t}
222
- * i18nKey="messages.unread"
223
- * values={{ count: 5 }}
224
- * components={{
225
- * bold: (children) => <strong>{children}</strong>,
226
- * }}
227
- * />
228
- * // Renders: You have <strong>5</strong> unread messages
229
- *
230
- * @example
231
- * // Translation: "Read our <terms>terms of service</terms> and <privacy>privacy policy</privacy>"
232
- * <Trans
233
- * t={t}
234
- * i18nKey="legal"
235
- * components={{
236
- * terms: (children) => <a href="/terms">{children}</a>,
237
- * privacy: (children) => <a href="/privacy">{children}</a>,
238
- * }}
239
- * />
240
- */
241
- declare function Trans(props: TransProps): VNode | string;
242
- //#endregion
243
- export { I18nContext, type I18nInstance, type I18nOptions, I18nProvider, type I18nProviderProps, type InterpolationValues, type NamespaceLoader, type PluralRules, Trans, type TransProps, type TranslationDictionary, type TranslationMessages, createI18n, interpolate, parseRichText, resolvePluralCategory, useI18n };
244
- //# sourceMappingURL=index2.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/types.ts","../../src/create-i18n.ts","../../src/interpolation.ts","../../src/pluralization.ts","../../src/context.ts","../../src/trans.ts"],"mappings":";;;;;;KAGY,qBAAA;EAAA,CACT,GAAA,oBAAuB,qBAAA;AAAA;AAD1B;AAAA,KAKY,mBAAA,GAAsB,MAAA,SAAe,qBAAA;;;;AAAjD;KAMY,eAAA,IACV,MAAA,UACA,SAAA,aACG,OAAA,CAAQ,qBAAA;;KAGD,mBAAA,GAAsB,MAAA;;KAGtB,WAAA,GAAc,MAAA,UAAgB,KAAA;;UAGzB,WAAA;EATL;EAWV,MAAA;EAZA;EAcA,cAAA;EAbW;EAeX,QAAA,GAAW,MAAA,SAAe,qBAAA;EAfM;AAGlC;;;EAiBE,MAAA,GAAS,eAAA;EAjB6B;AAGxC;;;EAmBE,gBAAA;EAnBqD;AAGvD;;;EAqBE,WAAA,GAAc,WAAA;EAfH;;;;EAoBX,YAAA,IACE,MAAA,UACA,GAAA,UACA,SAAA;AAAA;;UAKa,YAAA;EA5BJ;;;;;;;;;;EAuCX,CAAA,GAAI,GAAA,UAAa,MAAA,GAAS,mBAAA;EAhBN;EAmBpB,MAAA,EAAQ,MAAA;EAdO;;;;EAoBf,aAAA,GAAgB,SAAA,UAAmB,MAAA,cAAoB,OAAA;EAAA;;;EAKvD,SAAA,EAAW,QAAA;EAkBC;;;EAbZ,gBAAA,EAAkB,QAAA,CAAS,GAAA;EAnB3B;;;EAwBA,MAAA,GAAS,GAAA;EArBT;;;;EA2BA,WAAA,GACE,MAAA,UACA,QAAA,EAAU,qBAAA,EACV,SAAA;EAxBqD;;;EA8BvD,gBAAA,EAAkB,QAAA;AAAA;;;;;;;AA3GpB;;;;;AAKA;;;;;AAMA;;;;;;;;;;AAMA;;;;;AAGA;iBCgEgB,UAAA,CAAW,OAAA,EAAS,WAAA,GAAc,YAAA;;;;;;;ADpFlD;iBEMgB,WAAA,CACd,QAAA,UACA,MAAA,GAAS,mBAAA;;;;;;;AFRX;;iBGKgB,qBAAA,CACd,MAAA,UACA,KAAA,UACA,WAAA,GAAc,WAAA;;;cCDH,WAAA,EAAW,aAAA,CAAA,OAAA,CAAA,YAAA;AAAA,UAEP,iBAAA,SAA0B,KAAA;EACzC,QAAA,EAAU,YAAA;EACV,QAAA,GAAW,UAAA;AAAA;;;;AJNb;;;;;AAMA;;;iBIcgB,YAAA,CAAa,KAAA,EAAO,iBAAA,GAAoB,KAAA;;;;;;;AJRxD;;;;iBI4BgB,OAAA,CAAA,GAAW,YAAA;;;UC1CjB,QAAA;EACR,GAAA;EACA,QAAA;AAAA;;;;;ALAF;;;iBKUgB,aAAA,CAAc,IAAA,qBAAyB,QAAA;AAAA,UAiBtC,UAAA,SAAmB,KAAA;ELrBxB;EKuBV,OAAA;;EAEA,MAAA,GAAS,mBAAA;ELxBT;;;;;;AAKF;;;;;AAGA;EK6BE,UAAA,GAAa,MAAA,UAAgB,QAAA;;;;AL1B/B;EK+BE,CAAA,GAAI,GAAA,UAAa,MAAA,GAAS,mBAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;ALG5B;;;;;;;;;iBK+BgB,KAAA,CAAM,KAAA,EAAO,UAAA,GAAa,KAAA"}