@zachhandley/ez-i18n 0.3.3 → 0.3.5
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 +69 -0
- package/dist/index.d.ts +38 -1
- package/dist/index.js +186 -1
- package/package.json +2 -2
- package/src/index.ts +0 -122
- package/src/middleware.ts +0 -82
- package/src/runtime/index.ts +0 -19
- package/src/runtime/store.ts +0 -122
- package/src/types.ts +0 -125
- package/src/utils/index.ts +0 -16
- package/src/utils/translations.ts +0 -418
- package/src/virtual.d.ts +0 -64
- package/src/vite-plugin.ts +0 -698
package/README.md
CHANGED
|
@@ -76,6 +76,75 @@ t('common.countdown', { seconds: 5 }); // "Ready in 5 seconds"
|
|
|
76
76
|
await setLocale('es');
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
## Virtual Modules
|
|
80
|
+
|
|
81
|
+
### `ez-i18n:runtime`
|
|
82
|
+
|
|
83
|
+
Core translation functions:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { t, locale, setLocale, initLocale } from 'ez-i18n:runtime';
|
|
87
|
+
|
|
88
|
+
t('key'); // Translate a key
|
|
89
|
+
t('key', { name: 'World' }); // With interpolation
|
|
90
|
+
locale; // Reactive store with current locale
|
|
91
|
+
await setLocale('es'); // Change locale (persists to cookie)
|
|
92
|
+
initLocale('en', data); // Initialize with translations
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### `ez-i18n:config`
|
|
96
|
+
|
|
97
|
+
Access your i18n configuration:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import {
|
|
101
|
+
locales, // ['en', 'es', 'fr']
|
|
102
|
+
defaultLocale, // 'en'
|
|
103
|
+
cookieName, // 'ez-locale'
|
|
104
|
+
localeNames, // { en: 'English', es: 'Español', fr: 'Français' }
|
|
105
|
+
localeToBCP47, // { en: 'en-US', es: 'es-ES', fr: 'fr-FR' }
|
|
106
|
+
localeDirections, // { en: 'ltr', es: 'ltr', ar: 'rtl' }
|
|
107
|
+
} from 'ez-i18n:config';
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `ez-i18n:translations`
|
|
111
|
+
|
|
112
|
+
Dynamic translation loading:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { loadTranslations, translationLoaders } from 'ez-i18n:translations';
|
|
116
|
+
|
|
117
|
+
const data = await loadTranslations('es');
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Locale Utilities
|
|
121
|
+
|
|
122
|
+
The package includes a comprehensive locale database with 100+ languages:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import {
|
|
126
|
+
LOCALE_DATABASE,
|
|
127
|
+
getLocaleInfo,
|
|
128
|
+
buildLocaleNames,
|
|
129
|
+
buildLocaleToBCP47,
|
|
130
|
+
buildLocaleDirections,
|
|
131
|
+
} from '@zachhandley/ez-i18n';
|
|
132
|
+
import type { LocaleInfo } from '@zachhandley/ez-i18n';
|
|
133
|
+
|
|
134
|
+
// Get info for any locale
|
|
135
|
+
const info = getLocaleInfo('es');
|
|
136
|
+
// { name: 'Español', englishName: 'Spanish', bcp47: 'es-ES', dir: 'ltr' }
|
|
137
|
+
|
|
138
|
+
// Build mappings for your supported locales
|
|
139
|
+
const names = buildLocaleNames(['en', 'es', 'ar']);
|
|
140
|
+
// { en: 'English', es: 'Español', ar: 'العربية' }
|
|
141
|
+
|
|
142
|
+
const directions = buildLocaleDirections(['en', 'es', 'ar']);
|
|
143
|
+
// { en: 'ltr', es: 'ltr', ar: 'rtl' }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Includes native display names, English names, BCP47 codes, and text direction (LTR/RTL) for all major languages and regional variants.
|
|
147
|
+
|
|
79
148
|
## Framework Bindings
|
|
80
149
|
|
|
81
150
|
- React: `@zachhandley/ez-i18n-react`
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,43 @@ import { AstroIntegration } from 'astro';
|
|
|
2
2
|
import { E as EzI18nConfig } from './types-Cg77gLzO.js';
|
|
3
3
|
export { T as TranslateFunction } from './types-Cg77gLzO.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Comprehensive locale database for ez-i18n
|
|
7
|
+
* Contains display names (in native language) and BCP47 codes
|
|
8
|
+
*/
|
|
9
|
+
interface LocaleInfo {
|
|
10
|
+
/** Display name in native language */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Display name in English */
|
|
13
|
+
englishName: string;
|
|
14
|
+
/** BCP47 language tag */
|
|
15
|
+
bcp47: string;
|
|
16
|
+
/** Text direction */
|
|
17
|
+
dir: 'ltr' | 'rtl';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Comprehensive mapping of locale codes to their metadata
|
|
21
|
+
* Covers all major languages and regional variants
|
|
22
|
+
*/
|
|
23
|
+
declare const LOCALE_DATABASE: Record<string, LocaleInfo>;
|
|
24
|
+
/**
|
|
25
|
+
* Get locale info for a given locale code
|
|
26
|
+
* Falls back to a generated entry if not found
|
|
27
|
+
*/
|
|
28
|
+
declare function getLocaleInfo(locale: string): LocaleInfo;
|
|
29
|
+
/**
|
|
30
|
+
* Build locale names mapping for discovered locales
|
|
31
|
+
*/
|
|
32
|
+
declare function buildLocaleNames(locales: string[]): Record<string, string>;
|
|
33
|
+
/**
|
|
34
|
+
* Build locale to BCP47 mapping for discovered locales
|
|
35
|
+
*/
|
|
36
|
+
declare function buildLocaleToBCP47(locales: string[]): Record<string, string>;
|
|
37
|
+
/**
|
|
38
|
+
* Build locale directions mapping for discovered locales
|
|
39
|
+
*/
|
|
40
|
+
declare function buildLocaleDirections(locales: string[]): Record<string, 'ltr' | 'rtl'>;
|
|
41
|
+
|
|
5
42
|
/**
|
|
6
43
|
* ez-i18n Astro integration
|
|
7
44
|
*
|
|
@@ -26,4 +63,4 @@ export { T as TranslateFunction } from './types-Cg77gLzO.js';
|
|
|
26
63
|
*/
|
|
27
64
|
declare function ezI18n(config: EzI18nConfig): AstroIntegration;
|
|
28
65
|
|
|
29
|
-
export { EzI18nConfig, ezI18n as default };
|
|
66
|
+
export { EzI18nConfig, LOCALE_DATABASE, type LocaleInfo, buildLocaleDirections, buildLocaleNames, buildLocaleToBCP47, ezI18n as default, getLocaleInfo };
|
package/dist/index.js
CHANGED
|
@@ -194,6 +194,168 @@ function __wrapWithNamespace(namespace, content) {
|
|
|
194
194
|
}`;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
// src/utils/locales.ts
|
|
198
|
+
var LOCALE_DATABASE = {
|
|
199
|
+
// Major Western European Languages
|
|
200
|
+
en: { name: "English", englishName: "English", bcp47: "en-US", dir: "ltr" },
|
|
201
|
+
"en-US": { name: "English (US)", englishName: "English (US)", bcp47: "en-US", dir: "ltr" },
|
|
202
|
+
"en-GB": { name: "English (UK)", englishName: "English (UK)", bcp47: "en-GB", dir: "ltr" },
|
|
203
|
+
"en-AU": { name: "English (Australia)", englishName: "English (Australia)", bcp47: "en-AU", dir: "ltr" },
|
|
204
|
+
"en-CA": { name: "English (Canada)", englishName: "English (Canada)", bcp47: "en-CA", dir: "ltr" },
|
|
205
|
+
de: { name: "Deutsch", englishName: "German", bcp47: "de-DE", dir: "ltr" },
|
|
206
|
+
"de-DE": { name: "Deutsch (Deutschland)", englishName: "German (Germany)", bcp47: "de-DE", dir: "ltr" },
|
|
207
|
+
"de-AT": { name: "Deutsch (\xD6sterreich)", englishName: "German (Austria)", bcp47: "de-AT", dir: "ltr" },
|
|
208
|
+
"de-CH": { name: "Deutsch (Schweiz)", englishName: "German (Switzerland)", bcp47: "de-CH", dir: "ltr" },
|
|
209
|
+
fr: { name: "Fran\xE7ais", englishName: "French", bcp47: "fr-FR", dir: "ltr" },
|
|
210
|
+
"fr-FR": { name: "Fran\xE7ais (France)", englishName: "French (France)", bcp47: "fr-FR", dir: "ltr" },
|
|
211
|
+
"fr-CA": { name: "Fran\xE7ais (Canada)", englishName: "French (Canada)", bcp47: "fr-CA", dir: "ltr" },
|
|
212
|
+
"fr-BE": { name: "Fran\xE7ais (Belgique)", englishName: "French (Belgium)", bcp47: "fr-BE", dir: "ltr" },
|
|
213
|
+
"fr-CH": { name: "Fran\xE7ais (Suisse)", englishName: "French (Switzerland)", bcp47: "fr-CH", dir: "ltr" },
|
|
214
|
+
es: { name: "Espa\xF1ol", englishName: "Spanish", bcp47: "es-ES", dir: "ltr" },
|
|
215
|
+
"es-ES": { name: "Espa\xF1ol (Espa\xF1a)", englishName: "Spanish (Spain)", bcp47: "es-ES", dir: "ltr" },
|
|
216
|
+
"es-MX": { name: "Espa\xF1ol (M\xE9xico)", englishName: "Spanish (Mexico)", bcp47: "es-MX", dir: "ltr" },
|
|
217
|
+
"es-AR": { name: "Espa\xF1ol (Argentina)", englishName: "Spanish (Argentina)", bcp47: "es-AR", dir: "ltr" },
|
|
218
|
+
"es-CO": { name: "Espa\xF1ol (Colombia)", englishName: "Spanish (Colombia)", bcp47: "es-CO", dir: "ltr" },
|
|
219
|
+
"es-419": { name: "Espa\xF1ol (Latinoam\xE9rica)", englishName: "Spanish (Latin America)", bcp47: "es-419", dir: "ltr" },
|
|
220
|
+
it: { name: "Italiano", englishName: "Italian", bcp47: "it-IT", dir: "ltr" },
|
|
221
|
+
"it-IT": { name: "Italiano (Italia)", englishName: "Italian (Italy)", bcp47: "it-IT", dir: "ltr" },
|
|
222
|
+
"it-CH": { name: "Italiano (Svizzera)", englishName: "Italian (Switzerland)", bcp47: "it-CH", dir: "ltr" },
|
|
223
|
+
pt: { name: "Portugu\xEAs", englishName: "Portuguese", bcp47: "pt-PT", dir: "ltr" },
|
|
224
|
+
"pt-PT": { name: "Portugu\xEAs (Portugal)", englishName: "Portuguese (Portugal)", bcp47: "pt-PT", dir: "ltr" },
|
|
225
|
+
"pt-BR": { name: "Portugu\xEAs (Brasil)", englishName: "Portuguese (Brazil)", bcp47: "pt-BR", dir: "ltr" },
|
|
226
|
+
nl: { name: "Nederlands", englishName: "Dutch", bcp47: "nl-NL", dir: "ltr" },
|
|
227
|
+
"nl-NL": { name: "Nederlands (Nederland)", englishName: "Dutch (Netherlands)", bcp47: "nl-NL", dir: "ltr" },
|
|
228
|
+
"nl-BE": { name: "Nederlands (Belgi\xEB)", englishName: "Dutch (Belgium)", bcp47: "nl-BE", dir: "ltr" },
|
|
229
|
+
// Nordic Languages
|
|
230
|
+
sv: { name: "Svenska", englishName: "Swedish", bcp47: "sv-SE", dir: "ltr" },
|
|
231
|
+
"sv-SE": { name: "Svenska (Sverige)", englishName: "Swedish (Sweden)", bcp47: "sv-SE", dir: "ltr" },
|
|
232
|
+
da: { name: "Dansk", englishName: "Danish", bcp47: "da-DK", dir: "ltr" },
|
|
233
|
+
"da-DK": { name: "Dansk (Danmark)", englishName: "Danish (Denmark)", bcp47: "da-DK", dir: "ltr" },
|
|
234
|
+
no: { name: "Norsk", englishName: "Norwegian", bcp47: "nb-NO", dir: "ltr" },
|
|
235
|
+
nb: { name: "Norsk bokm\xE5l", englishName: "Norwegian Bokm\xE5l", bcp47: "nb-NO", dir: "ltr" },
|
|
236
|
+
nn: { name: "Norsk nynorsk", englishName: "Norwegian Nynorsk", bcp47: "nn-NO", dir: "ltr" },
|
|
237
|
+
fi: { name: "Suomi", englishName: "Finnish", bcp47: "fi-FI", dir: "ltr" },
|
|
238
|
+
"fi-FI": { name: "Suomi (Suomi)", englishName: "Finnish (Finland)", bcp47: "fi-FI", dir: "ltr" },
|
|
239
|
+
is: { name: "\xCDslenska", englishName: "Icelandic", bcp47: "is-IS", dir: "ltr" },
|
|
240
|
+
// Eastern European Languages
|
|
241
|
+
pl: { name: "Polski", englishName: "Polish", bcp47: "pl-PL", dir: "ltr" },
|
|
242
|
+
"pl-PL": { name: "Polski (Polska)", englishName: "Polish (Poland)", bcp47: "pl-PL", dir: "ltr" },
|
|
243
|
+
cs: { name: "\u010Ce\u0161tina", englishName: "Czech", bcp47: "cs-CZ", dir: "ltr" },
|
|
244
|
+
"cs-CZ": { name: "\u010Ce\u0161tina (\u010Cesko)", englishName: "Czech (Czech Republic)", bcp47: "cs-CZ", dir: "ltr" },
|
|
245
|
+
sk: { name: "Sloven\u010Dina", englishName: "Slovak", bcp47: "sk-SK", dir: "ltr" },
|
|
246
|
+
"sk-SK": { name: "Sloven\u010Dina (Slovensko)", englishName: "Slovak (Slovakia)", bcp47: "sk-SK", dir: "ltr" },
|
|
247
|
+
hu: { name: "Magyar", englishName: "Hungarian", bcp47: "hu-HU", dir: "ltr" },
|
|
248
|
+
"hu-HU": { name: "Magyar (Magyarorsz\xE1g)", englishName: "Hungarian (Hungary)", bcp47: "hu-HU", dir: "ltr" },
|
|
249
|
+
ro: { name: "Rom\xE2n\u0103", englishName: "Romanian", bcp47: "ro-RO", dir: "ltr" },
|
|
250
|
+
"ro-RO": { name: "Rom\xE2n\u0103 (Rom\xE2nia)", englishName: "Romanian (Romania)", bcp47: "ro-RO", dir: "ltr" },
|
|
251
|
+
bg: { name: "\u0411\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438", englishName: "Bulgarian", bcp47: "bg-BG", dir: "ltr" },
|
|
252
|
+
"bg-BG": { name: "\u0411\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438 (\u0411\u044A\u043B\u0433\u0430\u0440\u0438\u044F)", englishName: "Bulgarian (Bulgaria)", bcp47: "bg-BG", dir: "ltr" },
|
|
253
|
+
hr: { name: "Hrvatski", englishName: "Croatian", bcp47: "hr-HR", dir: "ltr" },
|
|
254
|
+
sr: { name: "\u0421\u0440\u043F\u0441\u043A\u0438", englishName: "Serbian", bcp47: "sr-RS", dir: "ltr" },
|
|
255
|
+
sl: { name: "Sloven\u0161\u010Dina", englishName: "Slovenian", bcp47: "sl-SI", dir: "ltr" },
|
|
256
|
+
uk: { name: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430", englishName: "Ukrainian", bcp47: "uk-UA", dir: "ltr" },
|
|
257
|
+
"uk-UA": { name: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430 (\u0423\u043A\u0440\u0430\u0457\u043D\u0430)", englishName: "Ukrainian (Ukraine)", bcp47: "uk-UA", dir: "ltr" },
|
|
258
|
+
ru: { name: "\u0420\u0443\u0441\u0441\u043A\u0438\u0439", englishName: "Russian", bcp47: "ru-RU", dir: "ltr" },
|
|
259
|
+
"ru-RU": { name: "\u0420\u0443\u0441\u0441\u043A\u0438\u0439 (\u0420\u043E\u0441\u0441\u0438\u044F)", englishName: "Russian (Russia)", bcp47: "ru-RU", dir: "ltr" },
|
|
260
|
+
// Baltic Languages
|
|
261
|
+
lt: { name: "Lietuvi\u0173", englishName: "Lithuanian", bcp47: "lt-LT", dir: "ltr" },
|
|
262
|
+
lv: { name: "Latvie\u0161u", englishName: "Latvian", bcp47: "lv-LV", dir: "ltr" },
|
|
263
|
+
et: { name: "Eesti", englishName: "Estonian", bcp47: "et-EE", dir: "ltr" },
|
|
264
|
+
// Greek
|
|
265
|
+
el: { name: "\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC", englishName: "Greek", bcp47: "el-GR", dir: "ltr" },
|
|
266
|
+
"el-GR": { name: "\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC (\u0395\u03BB\u03BB\u03AC\u03B4\u03B1)", englishName: "Greek (Greece)", bcp47: "el-GR", dir: "ltr" },
|
|
267
|
+
// Asian Languages
|
|
268
|
+
zh: { name: "\u4E2D\u6587", englishName: "Chinese", bcp47: "zh-CN", dir: "ltr" },
|
|
269
|
+
"zh-CN": { name: "\u4E2D\u6587 (\u7B80\u4F53)", englishName: "Chinese (Simplified)", bcp47: "zh-CN", dir: "ltr" },
|
|
270
|
+
"zh-TW": { name: "\u4E2D\u6587 (\u7E41\u9AD4)", englishName: "Chinese (Traditional)", bcp47: "zh-TW", dir: "ltr" },
|
|
271
|
+
"zh-HK": { name: "\u4E2D\u6587 (\u9999\u6E2F)", englishName: "Chinese (Hong Kong)", bcp47: "zh-HK", dir: "ltr" },
|
|
272
|
+
ja: { name: "\u65E5\u672C\u8A9E", englishName: "Japanese", bcp47: "ja-JP", dir: "ltr" },
|
|
273
|
+
"ja-JP": { name: "\u65E5\u672C\u8A9E (\u65E5\u672C)", englishName: "Japanese (Japan)", bcp47: "ja-JP", dir: "ltr" },
|
|
274
|
+
ko: { name: "\uD55C\uAD6D\uC5B4", englishName: "Korean", bcp47: "ko-KR", dir: "ltr" },
|
|
275
|
+
"ko-KR": { name: "\uD55C\uAD6D\uC5B4 (\uB300\uD55C\uBBFC\uAD6D)", englishName: "Korean (South Korea)", bcp47: "ko-KR", dir: "ltr" },
|
|
276
|
+
vi: { name: "Ti\u1EBFng Vi\u1EC7t", englishName: "Vietnamese", bcp47: "vi-VN", dir: "ltr" },
|
|
277
|
+
"vi-VN": { name: "Ti\u1EBFng Vi\u1EC7t (Vi\u1EC7t Nam)", englishName: "Vietnamese (Vietnam)", bcp47: "vi-VN", dir: "ltr" },
|
|
278
|
+
th: { name: "\u0E44\u0E17\u0E22", englishName: "Thai", bcp47: "th-TH", dir: "ltr" },
|
|
279
|
+
"th-TH": { name: "\u0E44\u0E17\u0E22 (\u0E1B\u0E23\u0E30\u0E40\u0E17\u0E28\u0E44\u0E17\u0E22)", englishName: "Thai (Thailand)", bcp47: "th-TH", dir: "ltr" },
|
|
280
|
+
id: { name: "Bahasa Indonesia", englishName: "Indonesian", bcp47: "id-ID", dir: "ltr" },
|
|
281
|
+
"id-ID": { name: "Bahasa Indonesia (Indonesia)", englishName: "Indonesian (Indonesia)", bcp47: "id-ID", dir: "ltr" },
|
|
282
|
+
ms: { name: "Bahasa Melayu", englishName: "Malay", bcp47: "ms-MY", dir: "ltr" },
|
|
283
|
+
"ms-MY": { name: "Bahasa Melayu (Malaysia)", englishName: "Malay (Malaysia)", bcp47: "ms-MY", dir: "ltr" },
|
|
284
|
+
tl: { name: "Tagalog", englishName: "Tagalog", bcp47: "tl-PH", dir: "ltr" },
|
|
285
|
+
fil: { name: "Filipino", englishName: "Filipino", bcp47: "fil-PH", dir: "ltr" },
|
|
286
|
+
// South Asian Languages
|
|
287
|
+
hi: { name: "\u0939\u093F\u0928\u094D\u0926\u0940", englishName: "Hindi", bcp47: "hi-IN", dir: "ltr" },
|
|
288
|
+
"hi-IN": { name: "\u0939\u093F\u0928\u094D\u0926\u0940 (\u092D\u093E\u0930\u0924)", englishName: "Hindi (India)", bcp47: "hi-IN", dir: "ltr" },
|
|
289
|
+
bn: { name: "\u09AC\u09BE\u0982\u09B2\u09BE", englishName: "Bengali", bcp47: "bn-BD", dir: "ltr" },
|
|
290
|
+
"bn-BD": { name: "\u09AC\u09BE\u0982\u09B2\u09BE (\u09AC\u09BE\u0982\u09B2\u09BE\u09A6\u09C7\u09B6)", englishName: "Bengali (Bangladesh)", bcp47: "bn-BD", dir: "ltr" },
|
|
291
|
+
"bn-IN": { name: "\u09AC\u09BE\u0982\u09B2\u09BE (\u09AD\u09BE\u09B0\u09A4)", englishName: "Bengali (India)", bcp47: "bn-IN", dir: "ltr" },
|
|
292
|
+
ta: { name: "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD", englishName: "Tamil", bcp47: "ta-IN", dir: "ltr" },
|
|
293
|
+
te: { name: "\u0C24\u0C46\u0C32\u0C41\u0C17\u0C41", englishName: "Telugu", bcp47: "te-IN", dir: "ltr" },
|
|
294
|
+
mr: { name: "\u092E\u0930\u093E\u0920\u0940", englishName: "Marathi", bcp47: "mr-IN", dir: "ltr" },
|
|
295
|
+
gu: { name: "\u0A97\u0AC1\u0A9C\u0AB0\u0ABE\u0AA4\u0AC0", englishName: "Gujarati", bcp47: "gu-IN", dir: "ltr" },
|
|
296
|
+
kn: { name: "\u0C95\u0CA8\u0CCD\u0CA8\u0CA1", englishName: "Kannada", bcp47: "kn-IN", dir: "ltr" },
|
|
297
|
+
ml: { name: "\u0D2E\u0D32\u0D2F\u0D3E\u0D33\u0D02", englishName: "Malayalam", bcp47: "ml-IN", dir: "ltr" },
|
|
298
|
+
pa: { name: "\u0A2A\u0A70\u0A1C\u0A3E\u0A2C\u0A40", englishName: "Punjabi", bcp47: "pa-IN", dir: "ltr" },
|
|
299
|
+
ur: { name: "\u0627\u0631\u062F\u0648", englishName: "Urdu", bcp47: "ur-PK", dir: "rtl" },
|
|
300
|
+
// Middle Eastern Languages (RTL)
|
|
301
|
+
ar: { name: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629", englishName: "Arabic", bcp47: "ar-SA", dir: "rtl" },
|
|
302
|
+
"ar-SA": { name: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629 (\u0627\u0644\u0633\u0639\u0648\u062F\u064A\u0629)", englishName: "Arabic (Saudi Arabia)", bcp47: "ar-SA", dir: "rtl" },
|
|
303
|
+
"ar-EG": { name: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629 (\u0645\u0635\u0631)", englishName: "Arabic (Egypt)", bcp47: "ar-EG", dir: "rtl" },
|
|
304
|
+
"ar-AE": { name: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629 (\u0627\u0644\u0625\u0645\u0627\u0631\u0627\u062A)", englishName: "Arabic (UAE)", bcp47: "ar-AE", dir: "rtl" },
|
|
305
|
+
he: { name: "\u05E2\u05D1\u05E8\u05D9\u05EA", englishName: "Hebrew", bcp47: "he-IL", dir: "rtl" },
|
|
306
|
+
"he-IL": { name: "\u05E2\u05D1\u05E8\u05D9\u05EA (\u05D9\u05E9\u05E8\u05D0\u05DC)", englishName: "Hebrew (Israel)", bcp47: "he-IL", dir: "rtl" },
|
|
307
|
+
fa: { name: "\u0641\u0627\u0631\u0633\u06CC", englishName: "Persian", bcp47: "fa-IR", dir: "rtl" },
|
|
308
|
+
"fa-IR": { name: "\u0641\u0627\u0631\u0633\u06CC (\u0627\u06CC\u0631\u0627\u0646)", englishName: "Persian (Iran)", bcp47: "fa-IR", dir: "rtl" },
|
|
309
|
+
tr: { name: "T\xFCrk\xE7e", englishName: "Turkish", bcp47: "tr-TR", dir: "ltr" },
|
|
310
|
+
"tr-TR": { name: "T\xFCrk\xE7e (T\xFCrkiye)", englishName: "Turkish (Turkey)", bcp47: "tr-TR", dir: "ltr" },
|
|
311
|
+
// African Languages
|
|
312
|
+
sw: { name: "Kiswahili", englishName: "Swahili", bcp47: "sw-KE", dir: "ltr" },
|
|
313
|
+
af: { name: "Afrikaans", englishName: "Afrikaans", bcp47: "af-ZA", dir: "ltr" },
|
|
314
|
+
zu: { name: "isiZulu", englishName: "Zulu", bcp47: "zu-ZA", dir: "ltr" },
|
|
315
|
+
// Celtic Languages
|
|
316
|
+
cy: { name: "Cymraeg", englishName: "Welsh", bcp47: "cy-GB", dir: "ltr" },
|
|
317
|
+
ga: { name: "Gaeilge", englishName: "Irish", bcp47: "ga-IE", dir: "ltr" },
|
|
318
|
+
gd: { name: "G\xE0idhlig", englishName: "Scottish Gaelic", bcp47: "gd-GB", dir: "ltr" },
|
|
319
|
+
// Other European Languages
|
|
320
|
+
ca: { name: "Catal\xE0", englishName: "Catalan", bcp47: "ca-ES", dir: "ltr" },
|
|
321
|
+
eu: { name: "Euskara", englishName: "Basque", bcp47: "eu-ES", dir: "ltr" },
|
|
322
|
+
gl: { name: "Galego", englishName: "Galician", bcp47: "gl-ES", dir: "ltr" },
|
|
323
|
+
// Constructed Languages
|
|
324
|
+
eo: { name: "Esperanto", englishName: "Esperanto", bcp47: "eo", dir: "ltr" }
|
|
325
|
+
};
|
|
326
|
+
function getLocaleInfo(locale) {
|
|
327
|
+
if (LOCALE_DATABASE[locale]) {
|
|
328
|
+
return LOCALE_DATABASE[locale];
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
name: locale.toUpperCase(),
|
|
332
|
+
englishName: locale.toUpperCase(),
|
|
333
|
+
bcp47: locale,
|
|
334
|
+
dir: "ltr"
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function buildLocaleNames(locales) {
|
|
338
|
+
const names = {};
|
|
339
|
+
for (const locale of locales) {
|
|
340
|
+
names[locale] = getLocaleInfo(locale).name;
|
|
341
|
+
}
|
|
342
|
+
return names;
|
|
343
|
+
}
|
|
344
|
+
function buildLocaleToBCP47(locales) {
|
|
345
|
+
const bcp47 = {};
|
|
346
|
+
for (const locale of locales) {
|
|
347
|
+
bcp47[locale] = getLocaleInfo(locale).bcp47;
|
|
348
|
+
}
|
|
349
|
+
return bcp47;
|
|
350
|
+
}
|
|
351
|
+
function buildLocaleDirections(locales) {
|
|
352
|
+
const dirs = {};
|
|
353
|
+
for (const locale of locales) {
|
|
354
|
+
dirs[locale] = getLocaleInfo(locale).dir;
|
|
355
|
+
}
|
|
356
|
+
return dirs;
|
|
357
|
+
}
|
|
358
|
+
|
|
197
359
|
// src/vite-plugin.ts
|
|
198
360
|
import * as path2 from "path";
|
|
199
361
|
var VIRTUAL_CONFIG = "ez-i18n:config";
|
|
@@ -319,10 +481,22 @@ function vitePlugin(config) {
|
|
|
319
481
|
},
|
|
320
482
|
load(id) {
|
|
321
483
|
if (id === RESOLVED_PREFIX + VIRTUAL_CONFIG) {
|
|
484
|
+
const localeNames = buildLocaleNames(resolved.locales);
|
|
485
|
+
const localeToBCP47 = buildLocaleToBCP47(resolved.locales);
|
|
486
|
+
const localeDirections = buildLocaleDirections(resolved.locales);
|
|
322
487
|
return `
|
|
323
488
|
export const locales = ${JSON.stringify(resolved.locales)};
|
|
324
489
|
export const defaultLocale = ${JSON.stringify(resolved.defaultLocale)};
|
|
325
490
|
export const cookieName = ${JSON.stringify(resolved.cookieName)};
|
|
491
|
+
|
|
492
|
+
/** Display names for each locale (in native language) */
|
|
493
|
+
export const localeNames = ${JSON.stringify(localeNames)};
|
|
494
|
+
|
|
495
|
+
/** BCP47 language tags for each locale */
|
|
496
|
+
export const localeToBCP47 = ${JSON.stringify(localeToBCP47)};
|
|
497
|
+
|
|
498
|
+
/** Text direction for each locale ('ltr' or 'rtl') */
|
|
499
|
+
export const localeDirections = ${JSON.stringify(localeDirections)};
|
|
326
500
|
`;
|
|
327
501
|
}
|
|
328
502
|
if (id === RESOLVED_PREFIX + VIRTUAL_RUNTIME) {
|
|
@@ -769,6 +943,12 @@ function ezI18n(config) {
|
|
|
769
943
|
export const defaultLocale: string;
|
|
770
944
|
/** Cookie name used to store locale preference */
|
|
771
945
|
export const cookieName: string;
|
|
946
|
+
/** Display names for each locale (in native language) */
|
|
947
|
+
export const localeNames: Record<string, string>;
|
|
948
|
+
/** BCP47 language tags for each locale */
|
|
949
|
+
export const localeToBCP47: Record<string, string>;
|
|
950
|
+
/** Text direction for each locale ('ltr' or 'rtl') */
|
|
951
|
+
export const localeDirections: Record<string, 'ltr' | 'rtl'>;
|
|
772
952
|
}
|
|
773
953
|
|
|
774
954
|
declare module 'ez-i18n:runtime' {
|
|
@@ -808,5 +988,10 @@ declare module 'ez-i18n:translations' {
|
|
|
808
988
|
};
|
|
809
989
|
}
|
|
810
990
|
export {
|
|
811
|
-
|
|
991
|
+
LOCALE_DATABASE,
|
|
992
|
+
buildLocaleDirections,
|
|
993
|
+
buildLocaleNames,
|
|
994
|
+
buildLocaleToBCP47,
|
|
995
|
+
ezI18n as default,
|
|
996
|
+
getLocaleInfo
|
|
812
997
|
};
|
package/package.json
CHANGED
package/src/index.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import type { AstroIntegration, HookParameters } from 'astro';
|
|
2
|
-
import type { EzI18nConfig } from './types';
|
|
3
|
-
import { vitePlugin, resolveConfig } from './vite-plugin';
|
|
4
|
-
|
|
5
|
-
export type { EzI18nConfig, TranslateFunction } from './types';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* ez-i18n Astro integration
|
|
9
|
-
*
|
|
10
|
-
* Provides cookie-based i18n without URL prefixes.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* // astro.config.ts
|
|
14
|
-
* import ezI18n from '@zachhandley/ez-i18n';
|
|
15
|
-
*
|
|
16
|
-
* export default defineConfig({
|
|
17
|
-
* integrations: [
|
|
18
|
-
* ezI18n({
|
|
19
|
-
* locales: ['en', 'es', 'fr'],
|
|
20
|
-
* defaultLocale: 'en',
|
|
21
|
-
* translations: {
|
|
22
|
-
* en: './src/i18n/en.json',
|
|
23
|
-
* es: './src/i18n/es.json',
|
|
24
|
-
* },
|
|
25
|
-
* }),
|
|
26
|
-
* ],
|
|
27
|
-
* });
|
|
28
|
-
*/
|
|
29
|
-
export default function ezI18n(config: EzI18nConfig): AstroIntegration {
|
|
30
|
-
const resolved = resolveConfig(config);
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
name: 'ez-i18n',
|
|
34
|
-
hooks: {
|
|
35
|
-
'astro:config:setup': ({
|
|
36
|
-
updateConfig,
|
|
37
|
-
addMiddleware,
|
|
38
|
-
injectScript,
|
|
39
|
-
}: HookParameters<'astro:config:setup'>) => {
|
|
40
|
-
// Add Vite plugin for virtual modules
|
|
41
|
-
updateConfig({
|
|
42
|
-
vite: {
|
|
43
|
-
plugins: [vitePlugin(config)],
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Add locale detection middleware
|
|
48
|
-
addMiddleware({
|
|
49
|
-
entrypoint: '@zachhandley/ez-i18n/middleware',
|
|
50
|
-
order: 'pre',
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Inject hydration script to sync localStorage with cookie
|
|
54
|
-
// This prevents hydration mismatch when server and client disagree
|
|
55
|
-
const hydrationScript = `
|
|
56
|
-
(function() {
|
|
57
|
-
try {
|
|
58
|
-
var cookieName = ${JSON.stringify(resolved.cookieName)};
|
|
59
|
-
var stored = localStorage.getItem(cookieName);
|
|
60
|
-
var cookieMatch = document.cookie.match(new RegExp(cookieName + '=([^;]+)'));
|
|
61
|
-
var cookie = cookieMatch ? cookieMatch[1] : null;
|
|
62
|
-
if (cookie && stored !== cookie) {
|
|
63
|
-
localStorage.setItem(cookieName, cookie);
|
|
64
|
-
}
|
|
65
|
-
} catch (e) {}
|
|
66
|
-
})();
|
|
67
|
-
`;
|
|
68
|
-
injectScript('head-inline', hydrationScript);
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
'astro:config:done': ({
|
|
72
|
-
injectTypes,
|
|
73
|
-
}: HookParameters<'astro:config:done'>) => {
|
|
74
|
-
// Inject type declarations for virtual modules
|
|
75
|
-
injectTypes({
|
|
76
|
-
filename: 'virtual.d.ts',
|
|
77
|
-
content: `\
|
|
78
|
-
declare module 'ez-i18n:config' {
|
|
79
|
-
/** List of all supported locale codes */
|
|
80
|
-
export const locales: readonly string[];
|
|
81
|
-
/** Default locale when no preference is detected */
|
|
82
|
-
export const defaultLocale: string;
|
|
83
|
-
/** Cookie name used to store locale preference */
|
|
84
|
-
export const cookieName: string;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
declare module 'ez-i18n:runtime' {
|
|
88
|
-
import type { ReadableAtom } from 'nanostores';
|
|
89
|
-
/** Reactive store containing the current locale */
|
|
90
|
-
export const locale: ReadableAtom<string>;
|
|
91
|
-
/**
|
|
92
|
-
* Translate a key to the current locale
|
|
93
|
-
* @param key - Dot-notation key (e.g., 'common.welcome')
|
|
94
|
-
* @param params - Optional interpolation params for {placeholder} syntax
|
|
95
|
-
*/
|
|
96
|
-
export function t(key: string, params?: Record<string, string | number>): string;
|
|
97
|
-
/**
|
|
98
|
-
* Set the current locale and persist to cookie/localStorage
|
|
99
|
-
* @param locale - Locale code to switch to
|
|
100
|
-
* @param cookieName - Optional custom cookie name
|
|
101
|
-
*/
|
|
102
|
-
export function setLocale(locale: string, cookieName?: string): Promise<void>;
|
|
103
|
-
/**
|
|
104
|
-
* Initialize the locale store with translations
|
|
105
|
-
* @param locale - Initial locale code
|
|
106
|
-
* @param translations - Optional initial translations object
|
|
107
|
-
*/
|
|
108
|
-
export function initLocale(locale: string, translations?: Record<string, unknown>): void;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
declare module 'ez-i18n:translations' {
|
|
112
|
-
/** Load translations for a specific locale */
|
|
113
|
-
export function loadTranslations(locale: string): Promise<Record<string, unknown>>;
|
|
114
|
-
/** Get the translation loader map from config */
|
|
115
|
-
export const translationLoaders: Record<string, () => Promise<{ default: Record<string, unknown> }>>;
|
|
116
|
-
}
|
|
117
|
-
`,
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|
package/src/middleware.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { defineMiddleware } from 'astro:middleware';
|
|
2
|
-
import type { TranslateFunction } from './types';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Create a server-side translation function for the given translations object
|
|
6
|
-
*/
|
|
7
|
-
function createT(translations: Record<string, unknown>): TranslateFunction {
|
|
8
|
-
return (key: string, params?: Record<string, string | number>): string => {
|
|
9
|
-
const keys = key.split('.');
|
|
10
|
-
let value: unknown = translations;
|
|
11
|
-
for (const k of keys) {
|
|
12
|
-
if (value == null || typeof value !== 'object') return key;
|
|
13
|
-
value = (value as Record<string, unknown>)[k];
|
|
14
|
-
}
|
|
15
|
-
if (typeof value !== 'string') return key;
|
|
16
|
-
if (!params) return value;
|
|
17
|
-
return value.replace(/\{(\w+)\}/g, (_, p) => String(params[p] ?? `{${p}}`));
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Locale detection middleware for ez-i18n
|
|
23
|
-
*
|
|
24
|
-
* Detection priority:
|
|
25
|
-
* 1. ?lang query parameter (allows explicit switching)
|
|
26
|
-
* 2. Cookie value
|
|
27
|
-
* 3. Accept-Language header
|
|
28
|
-
* 4. Default locale
|
|
29
|
-
*/
|
|
30
|
-
export const onRequest = defineMiddleware(async ({ cookies, request, locals }, next) => {
|
|
31
|
-
// Import config from virtual module (provided by vite-plugin)
|
|
32
|
-
const { locales, defaultLocale, cookieName } = await import('ez-i18n:config');
|
|
33
|
-
|
|
34
|
-
const url = new URL(request.url);
|
|
35
|
-
|
|
36
|
-
// Priority 1: Query parameter
|
|
37
|
-
const langParam = url.searchParams.get('lang');
|
|
38
|
-
|
|
39
|
-
// Priority 2: Cookie
|
|
40
|
-
const cookieValue = cookies.get(cookieName)?.value;
|
|
41
|
-
|
|
42
|
-
// Priority 3: Accept-Language header
|
|
43
|
-
const acceptLang = request.headers.get('accept-language');
|
|
44
|
-
const browserLang = acceptLang?.split(',')[0]?.split('-')[0];
|
|
45
|
-
|
|
46
|
-
// Determine locale with priority
|
|
47
|
-
let locale = defaultLocale;
|
|
48
|
-
|
|
49
|
-
if (langParam && locales.includes(langParam)) {
|
|
50
|
-
locale = langParam;
|
|
51
|
-
} else if (cookieValue && locales.includes(cookieValue)) {
|
|
52
|
-
locale = cookieValue;
|
|
53
|
-
} else if (browserLang && locales.includes(browserLang)) {
|
|
54
|
-
locale = browserLang;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Set locale on locals for use in pages
|
|
58
|
-
locals.locale = locale;
|
|
59
|
-
|
|
60
|
-
// Load translations for the current locale
|
|
61
|
-
try {
|
|
62
|
-
const { loadTranslations } = await import('ez-i18n:translations');
|
|
63
|
-
locals.translations = await loadTranslations(locale);
|
|
64
|
-
} catch {
|
|
65
|
-
// Fallback to empty translations if loader not configured
|
|
66
|
-
locals.translations = {};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Create server-side translation function
|
|
70
|
-
locals.t = createT(locals.translations);
|
|
71
|
-
|
|
72
|
-
// Update cookie if changed via query param
|
|
73
|
-
if (langParam && langParam !== cookieValue && locales.includes(langParam)) {
|
|
74
|
-
cookies.set(cookieName, locale, {
|
|
75
|
-
path: '/',
|
|
76
|
-
maxAge: 60 * 60 * 24 * 365, // 1 year
|
|
77
|
-
sameSite: 'lax',
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return next();
|
|
82
|
-
});
|
package/src/runtime/index.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime exports for ez-i18n
|
|
3
|
-
*
|
|
4
|
-
* This module is imported by the ez-i18n:runtime virtual module
|
|
5
|
-
* and can also be used directly in Vue components
|
|
6
|
-
*/
|
|
7
|
-
export {
|
|
8
|
-
effectiveLocale,
|
|
9
|
-
translations,
|
|
10
|
-
localePreference,
|
|
11
|
-
localeLoading,
|
|
12
|
-
initLocale,
|
|
13
|
-
setLocale,
|
|
14
|
-
setTranslations,
|
|
15
|
-
getLocale,
|
|
16
|
-
getTranslations,
|
|
17
|
-
} from './store';
|
|
18
|
-
|
|
19
|
-
export type { TranslationLoader } from './store';
|