@zachhandley/ez-i18n 0.3.6 → 0.3.7
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 +25 -1
- package/dist/index.js +22 -1
- package/dist/runtime/index.d.ts +42 -2
- package/dist/runtime/index.js +44 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,10 +83,12 @@ await setLocale('es');
|
|
|
83
83
|
Core translation functions:
|
|
84
84
|
|
|
85
85
|
```ts
|
|
86
|
-
import { t, locale, setLocale, initLocale } from 'ez-i18n:runtime';
|
|
86
|
+
import { t, tc, locale, setLocale, initLocale } from 'ez-i18n:runtime';
|
|
87
87
|
|
|
88
88
|
t('key'); // Translate a key
|
|
89
89
|
t('key', { name: 'World' }); // With interpolation
|
|
90
|
+
tc('key'); // Returns ReadableAtom<string> for reactive subscriptions
|
|
91
|
+
tc('key', { name: 'World' }); // With interpolation (reactive)
|
|
90
92
|
locale; // Reactive store with current locale
|
|
91
93
|
await setLocale('es'); // Change locale (persists to cookie)
|
|
92
94
|
initLocale('en', data); // Initialize with translations
|
|
@@ -117,6 +119,28 @@ import { loadTranslations, translationLoaders } from 'ez-i18n:translations';
|
|
|
117
119
|
const data = await loadTranslations('es');
|
|
118
120
|
```
|
|
119
121
|
|
|
122
|
+
## Reactive Translations with tc()
|
|
123
|
+
|
|
124
|
+
The `tc()` function returns a nanostore computed atom (`ReadableAtom<string>`) that automatically updates when the locale or translation data changes. This is useful when:
|
|
125
|
+
|
|
126
|
+
- Translations may load asynchronously after component mount
|
|
127
|
+
- You need fine-grained reactivity for specific translation keys
|
|
128
|
+
- Using with framework bindings (Vue/React) that need reactive subscriptions
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { tc } from 'ez-i18n:runtime';
|
|
132
|
+
import { useStore } from '@nanostores/react'; // or @nanostores/vue
|
|
133
|
+
|
|
134
|
+
// Returns a ReadableAtom that updates when locale changes
|
|
135
|
+
const title = tc('welcome.title');
|
|
136
|
+
const greeting = tc('welcome.message', { name: 'Alice' });
|
|
137
|
+
|
|
138
|
+
// In React/Vue, use with framework-specific store hooks
|
|
139
|
+
const titleValue = useStore(title); // Automatically re-renders on locale change
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
For most use cases, the regular `t()` function is sufficient. Use `tc()` when you need explicit reactive subscriptions in your framework code.
|
|
143
|
+
|
|
120
144
|
## Locale Utilities
|
|
121
145
|
|
|
122
146
|
The package includes a comprehensive locale database with 100+ languages:
|
package/dist/index.js
CHANGED
|
@@ -501,6 +501,7 @@ export const localeDirections = ${JSON.stringify(localeDirections)};
|
|
|
501
501
|
}
|
|
502
502
|
if (id === RESOLVED_PREFIX + VIRTUAL_RUNTIME) {
|
|
503
503
|
return `
|
|
504
|
+
import { computed } from 'nanostores';
|
|
504
505
|
import { effectiveLocale, translations, setLocale, initLocale } from '@zachhandley/ez-i18n/runtime';
|
|
505
506
|
|
|
506
507
|
export { setLocale, initLocale };
|
|
@@ -530,7 +531,7 @@ function interpolate(str, params) {
|
|
|
530
531
|
}
|
|
531
532
|
|
|
532
533
|
/**
|
|
533
|
-
* Translate a key to the current locale
|
|
534
|
+
* Translate a key to the current locale (non-reactive)
|
|
534
535
|
* @param key - Dot-notation key (e.g., 'common.welcome')
|
|
535
536
|
* @param params - Optional interpolation params
|
|
536
537
|
*/
|
|
@@ -547,6 +548,26 @@ export function t(key, params) {
|
|
|
547
548
|
|
|
548
549
|
return interpolate(value, params);
|
|
549
550
|
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Create a reactive translation computed (nanostore computed atom)
|
|
554
|
+
* @param key - Dot-notation key (e.g., 'common.welcome')
|
|
555
|
+
* @param params - Optional interpolation params
|
|
556
|
+
*/
|
|
557
|
+
export function tc(key, params) {
|
|
558
|
+
return computed(translations, (trans) => {
|
|
559
|
+
const value = getNestedValue(trans, key);
|
|
560
|
+
|
|
561
|
+
if (typeof value !== 'string') {
|
|
562
|
+
if (import.meta.env.DEV) {
|
|
563
|
+
console.warn('[ez-i18n] Missing translation:', key);
|
|
564
|
+
}
|
|
565
|
+
return key;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return interpolate(value, params);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
550
571
|
`;
|
|
551
572
|
}
|
|
552
573
|
if (id === RESOLVED_PREFIX + VIRTUAL_TRANSLATIONS) {
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import * as nanostores from 'nanostores';
|
|
2
|
+
import { ReadableAtom } from 'nanostores';
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Get nested value from object using dot notation
|
|
6
|
+
* @example getNestedValue({ a: { b: 'hello' } }, 'a.b') // 'hello'
|
|
7
|
+
*/
|
|
8
|
+
declare function getNestedValue(obj: Record<string, unknown>, path: string): unknown;
|
|
9
|
+
/**
|
|
10
|
+
* Interpolate params into string using {placeholder} syntax
|
|
11
|
+
* @example interpolate('Hello {name}!', { name: 'World' }) // 'Hello World!'
|
|
12
|
+
*/
|
|
13
|
+
declare function interpolate(str: string, params?: Record<string, string | number>): string;
|
|
3
14
|
/**
|
|
4
15
|
* Client-side locale preference (persisted to localStorage)
|
|
5
16
|
*/
|
|
@@ -7,7 +18,7 @@ declare const localePreference: nanostores.WritableAtom<string>;
|
|
|
7
18
|
/**
|
|
8
19
|
* Effective locale - uses server locale if set, otherwise client preference
|
|
9
20
|
*/
|
|
10
|
-
declare const effectiveLocale:
|
|
21
|
+
declare const effectiveLocale: ReadableAtom<string>;
|
|
11
22
|
/**
|
|
12
23
|
* Current translations object (reactive)
|
|
13
24
|
*/
|
|
@@ -48,5 +59,34 @@ declare function getLocale(): string;
|
|
|
48
59
|
* Get current translations (non-reactive)
|
|
49
60
|
*/
|
|
50
61
|
declare function getTranslations(): Record<string, unknown>;
|
|
62
|
+
/**
|
|
63
|
+
* Translate a key to its value (non-reactive, imperative)
|
|
64
|
+
* Use this in event handlers, callbacks, or non-reactive contexts.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* const message = t('welcome.title');
|
|
68
|
+
* const greeting = t('welcome.hello', { name: 'World' });
|
|
69
|
+
*/
|
|
70
|
+
declare function t(key: string, params?: Record<string, string | number>): string;
|
|
71
|
+
/**
|
|
72
|
+
* Create a reactive translation computed (nanostore computed atom)
|
|
73
|
+
* Returns a ReadableAtom<string> that updates when translations change.
|
|
74
|
+
*
|
|
75
|
+
* Use this when you need a reactive translation that updates automatically:
|
|
76
|
+
* - In Vue/React components where translations may load after initial render
|
|
77
|
+
* - When locale changes should trigger re-renders
|
|
78
|
+
* - To avoid hydration mismatches in SSR
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* // Static key
|
|
82
|
+
* const $title = tc('welcome.title');
|
|
83
|
+
*
|
|
84
|
+
* // In Vue (with @nanostores/vue)
|
|
85
|
+
* const title = useStore(tc('welcome.title'));
|
|
86
|
+
*
|
|
87
|
+
* // In React (with @nanostores/react)
|
|
88
|
+
* const title = useStore(tc('welcome.title'));
|
|
89
|
+
*/
|
|
90
|
+
declare function tc(key: string, params?: Record<string, string | number>): ReadableAtom<string>;
|
|
51
91
|
|
|
52
|
-
export { type TranslationLoader, effectiveLocale, getLocale, getTranslations, initLocale, localeLoading, localePreference, setLocale, setTranslations, translations };
|
|
92
|
+
export { type TranslationLoader, effectiveLocale, getLocale, getNestedValue, getTranslations, initLocale, interpolate, localeLoading, localePreference, setLocale, setTranslations, t, tc, translations };
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
// src/runtime/store.ts
|
|
2
2
|
import { atom, computed } from "nanostores";
|
|
3
3
|
import { persistentAtom } from "@nanostores/persistent";
|
|
4
|
+
function getNestedValue(obj, path) {
|
|
5
|
+
const keys = path.split(".");
|
|
6
|
+
let value = obj;
|
|
7
|
+
for (const key of keys) {
|
|
8
|
+
if (value == null || typeof value !== "object") {
|
|
9
|
+
return void 0;
|
|
10
|
+
}
|
|
11
|
+
value = value[key];
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function interpolate(str, params) {
|
|
16
|
+
if (!params) return str;
|
|
17
|
+
return str.replace(/\{(\w+)\}/g, (match, key) => {
|
|
18
|
+
return key in params ? String(params[key]) : match;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
4
21
|
var serverLocale = atom(null);
|
|
5
22
|
var localePreference = persistentAtom("ez-locale", "en", {
|
|
6
23
|
encode: (value) => value,
|
|
@@ -61,14 +78,41 @@ function getLocale() {
|
|
|
61
78
|
function getTranslations() {
|
|
62
79
|
return translations.get();
|
|
63
80
|
}
|
|
81
|
+
function t(key, params) {
|
|
82
|
+
const trans = translations.get();
|
|
83
|
+
const value = getNestedValue(trans, key);
|
|
84
|
+
if (typeof value !== "string") {
|
|
85
|
+
if (typeof import.meta !== "undefined" && import.meta.env?.DEV) {
|
|
86
|
+
console.warn("[ez-i18n] Missing translation:", key);
|
|
87
|
+
}
|
|
88
|
+
return key;
|
|
89
|
+
}
|
|
90
|
+
return interpolate(value, params);
|
|
91
|
+
}
|
|
92
|
+
function tc(key, params) {
|
|
93
|
+
return computed(translations, (trans) => {
|
|
94
|
+
const value = getNestedValue(trans, key);
|
|
95
|
+
if (typeof value !== "string") {
|
|
96
|
+
if (typeof import.meta !== "undefined" && import.meta.env?.DEV) {
|
|
97
|
+
console.warn("[ez-i18n] Missing translation:", key);
|
|
98
|
+
}
|
|
99
|
+
return key;
|
|
100
|
+
}
|
|
101
|
+
return interpolate(value, params);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
64
104
|
export {
|
|
65
105
|
effectiveLocale,
|
|
66
106
|
getLocale,
|
|
107
|
+
getNestedValue,
|
|
67
108
|
getTranslations,
|
|
68
109
|
initLocale,
|
|
110
|
+
interpolate,
|
|
69
111
|
localeLoading,
|
|
70
112
|
localePreference,
|
|
71
113
|
setLocale,
|
|
72
114
|
setTranslations,
|
|
115
|
+
t,
|
|
116
|
+
tc,
|
|
73
117
|
translations
|
|
74
118
|
};
|