@coherent.js/i18n 1.0.0-beta.5 → 1.0.0-beta.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/dist/formatters.js +321 -0
- package/dist/formatters.js.map +7 -0
- package/dist/index.js.map +7 -0
- package/dist/locale.js +215 -0
- package/dist/locale.js.map +7 -0
- package/dist/translator.js +220 -0
- package/dist/translator.js.map +7 -0
- package/package.json +13 -3
- package/types/index.d.ts +354 -6
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/locale.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Coherent.js Locale Utilities\n * \n * Utilities for locale detection and management\n * \n * @module i18n/locale\n */\n\n/**\n * Detect browser locale\n * \n * @returns {string} Detected locale code\n */\nexport function detectLocale() {\n if (typeof navigator !== 'undefined') {\n // Try navigator.language first\n if (navigator.language) {\n return normalizeLocale(navigator.language);\n }\n \n // Try navigator.languages array\n if (navigator.languages && navigator.languages.length > 0) {\n return normalizeLocale(navigator.languages[0]);\n }\n \n // Fallback to userLanguage (IE)\n if (navigator.userLanguage) {\n return normalizeLocale(navigator.userLanguage);\n }\n }\n \n // Default fallback\n return 'en';\n}\n\n/**\n * Normalize locale code\n * Converts various formats to standard format (e.g., 'en-US' -> 'en')\n * \n * @param {string} locale - Locale code\n * @param {boolean} [keepRegion=false] - Keep region code\n * @returns {string} Normalized locale\n */\nexport function normalizeLocale(locale, keepRegion = false) {\n if (!locale) return 'en';\n \n // Convert to lowercase and replace underscores\n let normalized = locale.toLowerCase().replace('_', '-');\n \n // Extract language code\n if (!keepRegion && normalized.includes('-')) {\n normalized = normalized.split('-')[0];\n }\n \n return normalized;\n}\n\n/**\n * Parse locale into components\n * \n * @param {string} locale - Locale code\n * @returns {Object} Parsed locale components\n */\nexport function parseLocale(locale) {\n const normalized = locale.replace('_', '-');\n const parts = normalized.split('-');\n \n return {\n language: parts[0]?.toLowerCase() || 'en',\n region: parts[1]?.toUpperCase() || null,\n script: parts.length > 2 ? parts[1] : null,\n full: normalized\n };\n}\n\n/**\n * Get locale direction (LTR or RTL)\n * \n * @param {string} locale - Locale code\n * @returns {string} 'ltr' or 'rtl'\n */\nexport function getLocaleDirection(locale) {\n const rtlLocales = ['ar', 'he', 'fa', 'ur', 'yi'];\n const language = parseLocale(locale).language;\n \n return rtlLocales.includes(language) ? 'rtl' : 'ltr';\n}\n\n/**\n * Check if locale is RTL\n * \n * @param {string} locale - Locale code\n * @returns {boolean} True if RTL\n */\nexport function isRTL(locale) {\n return getLocaleDirection(locale) === 'rtl';\n}\n\n/**\n * Get locale display name\n * \n * @param {string} locale - Locale code\n * @param {string} [displayLocale] - Locale to display name in\n * @returns {string} Display name\n */\nexport function getLocaleDisplayName(locale, displayLocale = 'en') {\n if (typeof Intl !== 'undefined' && Intl.DisplayNames) {\n try {\n const displayNames = new Intl.DisplayNames([displayLocale], { type: 'language' });\n return displayNames.of(locale);\n } catch {\n // Fallback\n }\n }\n \n // Fallback to locale code\n return locale;\n}\n\n/**\n * Match locale from available locales\n * Finds best matching locale from available options\n * \n * @param {string} requestedLocale - Requested locale\n * @param {Array<string>} availableLocales - Available locales\n * @param {string} [defaultLocale='en'] - Default fallback\n * @returns {string} Best matching locale\n */\nexport function matchLocale(requestedLocale, availableLocales, defaultLocale = 'en') {\n const normalized = normalizeLocale(requestedLocale);\n \n // Exact match\n if (availableLocales.includes(normalized)) {\n return normalized;\n }\n \n // Try with region\n const withRegion = normalizeLocale(requestedLocale, true);\n if (availableLocales.includes(withRegion)) {\n return withRegion;\n }\n \n // Try language match (ignore region)\n const language = parseLocale(requestedLocale).language;\n const languageMatch = availableLocales.find(locale => \n parseLocale(locale).language === language\n );\n \n if (languageMatch) {\n return languageMatch;\n }\n \n // Fallback to default\n return availableLocales.includes(defaultLocale) ? defaultLocale : availableLocales[0];\n}\n\n/**\n * Get supported locales from browser\n * \n * @returns {Array<string>} Array of supported locales\n */\nexport function getSupportedLocales() {\n if (typeof navigator !== 'undefined' && navigator.languages) {\n return navigator.languages.map(locale => normalizeLocale(locale));\n }\n \n return [detectLocale()];\n}\n\n/**\n * Locale Manager\n * Manages locale state and persistence\n */\nexport class LocaleManager {\n constructor(options = {}) {\n this.options = {\n defaultLocale: 'en',\n availableLocales: ['en'],\n storageKey: 'coherent-locale',\n autoDetect: true,\n ...options\n };\n \n this.currentLocale = this.options.defaultLocale;\n this.listeners = [];\n \n // Auto-detect or load from storage\n if (this.options.autoDetect) {\n this.currentLocale = this.detectAndMatch();\n }\n \n this.loadFromStorage();\n }\n\n /**\n * Detect and match best locale\n */\n detectAndMatch() {\n const detected = detectLocale();\n return matchLocale(\n detected,\n this.options.availableLocales,\n this.options.defaultLocale\n );\n }\n\n /**\n * Get current locale\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Set locale\n */\n setLocale(locale) {\n const matched = matchLocale(\n locale,\n this.options.availableLocales,\n this.options.defaultLocale\n );\n \n if (matched !== this.currentLocale) {\n const oldLocale = this.currentLocale;\n this.currentLocale = matched;\n \n this.saveToStorage();\n this.notifyListeners(oldLocale, matched);\n }\n }\n\n /**\n * Add locale change listener\n */\n onChange(listener) {\n this.listeners.push(listener);\n \n // Return unsubscribe function\n return () => {\n const index = this.listeners.indexOf(listener);\n if (index > -1) {\n this.listeners.splice(index, 1);\n }\n };\n }\n\n /**\n * Notify listeners of locale change\n */\n notifyListeners(oldLocale, newLocale) {\n this.listeners.forEach(listener => {\n try {\n listener(newLocale, oldLocale);\n } catch (error) {\n console.error('Error in locale change listener:', error);\n }\n });\n }\n\n /**\n * Save locale to storage\n */\n saveToStorage() {\n if (typeof localStorage !== 'undefined') {\n try {\n localStorage.setItem(this.options.storageKey, this.currentLocale);\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n /**\n * Load locale from storage\n */\n loadFromStorage() {\n if (typeof localStorage !== 'undefined') {\n try {\n const stored = localStorage.getItem(this.options.storageKey);\n if (stored) {\n this.setLocale(stored);\n }\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n /**\n * Get available locales\n */\n getAvailableLocales() {\n return [...this.options.availableLocales];\n }\n\n /**\n * Check if locale is available\n */\n isAvailable(locale) {\n return this.options.availableLocales.includes(locale);\n }\n}\n\n/**\n * Create a locale manager\n */\nexport function createLocaleManager(options = {}) {\n return new LocaleManager(options);\n}\n\nexport default {\n detectLocale,\n normalizeLocale,\n parseLocale,\n getLocaleDirection,\n isRTL,\n getLocaleDisplayName,\n matchLocale,\n getSupportedLocales,\n LocaleManager,\n createLocaleManager\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// src/translator.js
|
|
2
|
+
var Translator = class {
|
|
3
|
+
constructor(options = {}) {
|
|
4
|
+
this.options = {
|
|
5
|
+
defaultLocale: "en",
|
|
6
|
+
fallbackLocale: "en",
|
|
7
|
+
missingKeyHandler: null,
|
|
8
|
+
interpolation: {
|
|
9
|
+
prefix: "{{",
|
|
10
|
+
suffix: "}}"
|
|
11
|
+
},
|
|
12
|
+
...options
|
|
13
|
+
};
|
|
14
|
+
this.translations = /* @__PURE__ */ new Map();
|
|
15
|
+
this.currentLocale = this.options.defaultLocale;
|
|
16
|
+
this.loadedLocales = /* @__PURE__ */ new Set();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Add translations for a locale
|
|
20
|
+
*
|
|
21
|
+
* @param {string} locale - Locale code (e.g., 'en', 'fr', 'es')
|
|
22
|
+
* @param {Object} translations - Translation object
|
|
23
|
+
*/
|
|
24
|
+
addTranslations(locale, translations) {
|
|
25
|
+
if (!this.translations.has(locale)) {
|
|
26
|
+
this.translations.set(locale, {});
|
|
27
|
+
}
|
|
28
|
+
const existing = this.translations.get(locale);
|
|
29
|
+
this.translations.set(locale, this.deepMerge(existing, translations));
|
|
30
|
+
this.loadedLocales.add(locale);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Deep merge objects
|
|
34
|
+
*/
|
|
35
|
+
deepMerge(target, source) {
|
|
36
|
+
const result = { ...target };
|
|
37
|
+
for (const [key, value] of Object.entries(source)) {
|
|
38
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
39
|
+
result[key] = this.deepMerge(result[key] || {}, value);
|
|
40
|
+
} else {
|
|
41
|
+
result[key] = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Set current locale
|
|
48
|
+
*
|
|
49
|
+
* @param {string} locale - Locale code
|
|
50
|
+
*/
|
|
51
|
+
setLocale(locale) {
|
|
52
|
+
if (!this.loadedLocales.has(locale)) {
|
|
53
|
+
console.warn(`Locale ${locale} not loaded, using fallback`);
|
|
54
|
+
this.currentLocale = this.options.fallbackLocale;
|
|
55
|
+
} else {
|
|
56
|
+
this.currentLocale = locale;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get current locale
|
|
61
|
+
*
|
|
62
|
+
* @returns {string} Current locale code
|
|
63
|
+
*/
|
|
64
|
+
getLocale() {
|
|
65
|
+
return this.currentLocale;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Translate a key
|
|
69
|
+
*
|
|
70
|
+
* @param {string} key - Translation key (supports dot notation)
|
|
71
|
+
* @param {Object} [params] - Interpolation parameters
|
|
72
|
+
* @param {string} [locale] - Override locale
|
|
73
|
+
* @returns {string} Translated string
|
|
74
|
+
*/
|
|
75
|
+
t(key, params = {}, locale = null) {
|
|
76
|
+
const targetLocale = locale || this.currentLocale;
|
|
77
|
+
let translation = this.getTranslation(key, targetLocale);
|
|
78
|
+
if (translation === null && targetLocale !== this.options.fallbackLocale) {
|
|
79
|
+
translation = this.getTranslation(key, this.options.fallbackLocale);
|
|
80
|
+
}
|
|
81
|
+
if (translation === null) {
|
|
82
|
+
if (this.options.missingKeyHandler) {
|
|
83
|
+
return this.options.missingKeyHandler(key, targetLocale);
|
|
84
|
+
}
|
|
85
|
+
return key;
|
|
86
|
+
}
|
|
87
|
+
if (typeof translation === "object" && params.count !== void 0) {
|
|
88
|
+
translation = this.selectPlural(translation, params.count, targetLocale);
|
|
89
|
+
}
|
|
90
|
+
if (typeof translation === "string") {
|
|
91
|
+
return this.interpolate(translation, params);
|
|
92
|
+
}
|
|
93
|
+
return String(translation);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get translation from nested object
|
|
97
|
+
*/
|
|
98
|
+
getTranslation(key, locale) {
|
|
99
|
+
const translations = this.translations.get(locale);
|
|
100
|
+
if (!translations) return null;
|
|
101
|
+
const keys = key.split(".");
|
|
102
|
+
let value = translations;
|
|
103
|
+
for (const k of keys) {
|
|
104
|
+
if (value && typeof value === "object" && k in value) {
|
|
105
|
+
value = value[k];
|
|
106
|
+
} else {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Select plural form
|
|
114
|
+
*/
|
|
115
|
+
selectPlural(pluralObject, count, locale) {
|
|
116
|
+
if (count === 0 && pluralObject.zero) {
|
|
117
|
+
return pluralObject.zero;
|
|
118
|
+
}
|
|
119
|
+
if (typeof Intl !== "undefined" && Intl.PluralRules) {
|
|
120
|
+
const rules = new Intl.PluralRules(locale);
|
|
121
|
+
const rule = rules.select(count);
|
|
122
|
+
if (pluralObject[rule]) {
|
|
123
|
+
return pluralObject[rule];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (count === 1 && pluralObject.one) {
|
|
127
|
+
return pluralObject.one;
|
|
128
|
+
} else if (pluralObject.other) {
|
|
129
|
+
return pluralObject.other;
|
|
130
|
+
}
|
|
131
|
+
return pluralObject.one || pluralObject.other || "";
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Interpolate parameters into string
|
|
135
|
+
*/
|
|
136
|
+
interpolate(str, params) {
|
|
137
|
+
const { prefix, suffix } = this.options.interpolation;
|
|
138
|
+
let result = str;
|
|
139
|
+
for (const [key, value] of Object.entries(params)) {
|
|
140
|
+
const placeholder = `${prefix}${key}${suffix}`;
|
|
141
|
+
result = result.replace(new RegExp(placeholder, "g"), String(value));
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check if translation exists
|
|
147
|
+
*
|
|
148
|
+
* @param {string} key - Translation key
|
|
149
|
+
* @param {string} [locale] - Locale to check
|
|
150
|
+
* @returns {boolean} True if translation exists
|
|
151
|
+
*/
|
|
152
|
+
has(key, locale = null) {
|
|
153
|
+
const targetLocale = locale || this.currentLocale;
|
|
154
|
+
return this.getTranslation(key, targetLocale) !== null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get all translations for current locale
|
|
158
|
+
*
|
|
159
|
+
* @returns {Object} All translations
|
|
160
|
+
*/
|
|
161
|
+
getTranslations(locale = null) {
|
|
162
|
+
const targetLocale = locale || this.currentLocale;
|
|
163
|
+
return this.translations.get(targetLocale) || {};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get all loaded locales
|
|
167
|
+
*
|
|
168
|
+
* @returns {Array<string>} Array of locale codes
|
|
169
|
+
*/
|
|
170
|
+
getLoadedLocales() {
|
|
171
|
+
return Array.from(this.loadedLocales);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Remove translations for a locale
|
|
175
|
+
*
|
|
176
|
+
* @param {string} locale - Locale code
|
|
177
|
+
*/
|
|
178
|
+
removeLocale(locale) {
|
|
179
|
+
this.translations.delete(locale);
|
|
180
|
+
this.loadedLocales.delete(locale);
|
|
181
|
+
if (this.currentLocale === locale) {
|
|
182
|
+
this.currentLocale = this.options.defaultLocale;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Clear all translations
|
|
187
|
+
*/
|
|
188
|
+
clear() {
|
|
189
|
+
this.translations.clear();
|
|
190
|
+
this.loadedLocales.clear();
|
|
191
|
+
this.currentLocale = this.options.defaultLocale;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
function createTranslator(options = {}) {
|
|
195
|
+
return new Translator(options);
|
|
196
|
+
}
|
|
197
|
+
function createScopedTranslator(translator, namespace) {
|
|
198
|
+
return {
|
|
199
|
+
t: (key, params, locale) => {
|
|
200
|
+
return translator.t(`${namespace}.${key}`, params, locale);
|
|
201
|
+
},
|
|
202
|
+
has: (key, locale) => {
|
|
203
|
+
return translator.has(`${namespace}.${key}`, locale);
|
|
204
|
+
},
|
|
205
|
+
getLocale: () => translator.getLocale(),
|
|
206
|
+
setLocale: (locale) => translator.setLocale(locale)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
var translator_default = {
|
|
210
|
+
Translator,
|
|
211
|
+
createTranslator,
|
|
212
|
+
createScopedTranslator
|
|
213
|
+
};
|
|
214
|
+
export {
|
|
215
|
+
Translator,
|
|
216
|
+
createScopedTranslator,
|
|
217
|
+
createTranslator,
|
|
218
|
+
translator_default as default
|
|
219
|
+
};
|
|
220
|
+
//# sourceMappingURL=translator.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/translator.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Coherent.js Translator\n * \n * Handles translation of strings with interpolation and pluralization\n * \n * @module i18n/translator\n */\n\n/**\n * Translator\n * Manages translations and locale switching\n */\nexport class Translator {\n constructor(options = {}) {\n this.options = {\n defaultLocale: 'en',\n fallbackLocale: 'en',\n missingKeyHandler: null,\n interpolation: {\n prefix: '{{',\n suffix: '}}'\n },\n ...options\n };\n \n this.translations = new Map();\n this.currentLocale = this.options.defaultLocale;\n this.loadedLocales = new Set();\n }\n\n /**\n * Add translations for a locale\n * \n * @param {string} locale - Locale code (e.g., 'en', 'fr', 'es')\n * @param {Object} translations - Translation object\n */\n addTranslations(locale, translations) {\n if (!this.translations.has(locale)) {\n this.translations.set(locale, {});\n }\n \n const existing = this.translations.get(locale);\n this.translations.set(locale, this.deepMerge(existing, translations));\n this.loadedLocales.add(locale);\n }\n\n /**\n * Deep merge objects\n */\n deepMerge(target, source) {\n const result = { ...target };\n \n for (const [key, value] of Object.entries(source)) {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n result[key] = this.deepMerge(result[key] || {}, value);\n } else {\n result[key] = value;\n }\n }\n \n return result;\n }\n\n /**\n * Set current locale\n * \n * @param {string} locale - Locale code\n */\n setLocale(locale) {\n if (!this.loadedLocales.has(locale)) {\n console.warn(`Locale ${locale} not loaded, using fallback`);\n this.currentLocale = this.options.fallbackLocale;\n } else {\n this.currentLocale = locale;\n }\n }\n\n /**\n * Get current locale\n * \n * @returns {string} Current locale code\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Translate a key\n * \n * @param {string} key - Translation key (supports dot notation)\n * @param {Object} [params] - Interpolation parameters\n * @param {string} [locale] - Override locale\n * @returns {string} Translated string\n */\n t(key, params = {}, locale = null) {\n const targetLocale = locale || this.currentLocale;\n \n // Get translation\n let translation = this.getTranslation(key, targetLocale);\n \n // Fallback to default locale\n if (translation === null && targetLocale !== this.options.fallbackLocale) {\n translation = this.getTranslation(key, this.options.fallbackLocale);\n }\n \n // Handle missing translation\n if (translation === null) {\n if (this.options.missingKeyHandler) {\n return this.options.missingKeyHandler(key, targetLocale);\n }\n return key;\n }\n \n // Handle pluralization\n if (typeof translation === 'object' && params.count !== undefined) {\n translation = this.selectPlural(translation, params.count, targetLocale);\n }\n \n // Interpolate parameters\n if (typeof translation === 'string') {\n return this.interpolate(translation, params);\n }\n \n return String(translation);\n }\n\n /**\n * Get translation from nested object\n */\n getTranslation(key, locale) {\n const translations = this.translations.get(locale);\n if (!translations) return null;\n \n const keys = key.split('.');\n let value = translations;\n \n for (const k of keys) {\n if (value && typeof value === 'object' && k in value) {\n value = value[k];\n } else {\n return null;\n }\n }\n \n return value;\n }\n\n /**\n * Select plural form\n */\n selectPlural(pluralObject, count, locale) {\n // Check for explicit zero first (takes precedence over Intl rules)\n if (count === 0 && pluralObject.zero) {\n return pluralObject.zero;\n }\n \n // Use Intl.PluralRules for locale-specific pluralization\n if (typeof Intl !== 'undefined' && Intl.PluralRules) {\n const rules = new Intl.PluralRules(locale);\n const rule = rules.select(count);\n \n if (pluralObject[rule]) {\n return pluralObject[rule];\n }\n }\n \n // Fallback to simple rules\n if (count === 1 && pluralObject.one) {\n return pluralObject.one;\n } else if (pluralObject.other) {\n return pluralObject.other;\n }\n \n return pluralObject.one || pluralObject.other || '';\n }\n\n /**\n * Interpolate parameters into string\n */\n interpolate(str, params) {\n const { prefix, suffix } = this.options.interpolation;\n let result = str;\n \n for (const [key, value] of Object.entries(params)) {\n const placeholder = `${prefix}${key}${suffix}`;\n result = result.replace(new RegExp(placeholder, 'g'), String(value));\n }\n \n return result;\n }\n\n /**\n * Check if translation exists\n * \n * @param {string} key - Translation key\n * @param {string} [locale] - Locale to check\n * @returns {boolean} True if translation exists\n */\n has(key, locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.getTranslation(key, targetLocale) !== null;\n }\n\n /**\n * Get all translations for current locale\n * \n * @returns {Object} All translations\n */\n getTranslations(locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.translations.get(targetLocale) || {};\n }\n\n /**\n * Get all loaded locales\n * \n * @returns {Array<string>} Array of locale codes\n */\n getLoadedLocales() {\n return Array.from(this.loadedLocales);\n }\n\n /**\n * Remove translations for a locale\n * \n * @param {string} locale - Locale code\n */\n removeLocale(locale) {\n this.translations.delete(locale);\n this.loadedLocales.delete(locale);\n \n if (this.currentLocale === locale) {\n this.currentLocale = this.options.defaultLocale;\n }\n }\n\n /**\n * Clear all translations\n */\n clear() {\n this.translations.clear();\n this.loadedLocales.clear();\n this.currentLocale = this.options.defaultLocale;\n }\n}\n\n/**\n * Create a translator instance\n * \n * @param {Object} [options] - Translator options\n * @returns {Translator} Translator instance\n */\nexport function createTranslator(options = {}) {\n return new Translator(options);\n}\n\n/**\n * Create a scoped translator\n * Automatically prefixes all keys with a namespace\n * \n * @param {Translator} translator - Base translator\n * @param {string} namespace - Namespace prefix\n * @returns {Object} Scoped translator\n */\nexport function createScopedTranslator(translator, namespace) {\n return {\n t: (key, params, locale) => {\n return translator.t(`${namespace}.${key}`, params, locale);\n },\n has: (key, locale) => {\n return translator.has(`${namespace}.${key}`, locale);\n },\n getLocale: () => translator.getLocale(),\n setLocale: (locale) => translator.setLocale(locale)\n };\n}\n\nexport default {\n Translator,\n createTranslator,\n createScopedTranslator\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coherent.js/i18n",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.7",
|
|
4
4
|
"description": "Internationalization support for Coherent.js applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,17 +20,25 @@
|
|
|
20
20
|
"author": "Coherent.js Team",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@coherent.js/core": "1.0.0-beta.
|
|
23
|
+
"@coherent.js/core": "1.0.0-beta.7"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
27
27
|
"url": "git+https://github.com/Tomdrouv1/coherent.js.git"
|
|
28
28
|
},
|
|
29
|
+
"homepage": "https://github.com/Tomdrouv1/coherent.js",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/Tomdrouv1/coherent.js/issues"
|
|
32
|
+
},
|
|
29
33
|
"publishConfig": {
|
|
30
34
|
"access": "public"
|
|
31
35
|
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20.0.0"
|
|
38
|
+
},
|
|
32
39
|
"types": "./types/index.d.ts",
|
|
33
40
|
"files": [
|
|
41
|
+
"dist/",
|
|
34
42
|
"LICENSE",
|
|
35
43
|
"README.md",
|
|
36
44
|
"types/"
|
|
@@ -38,6 +46,8 @@
|
|
|
38
46
|
"sideEffects": false,
|
|
39
47
|
"scripts": {
|
|
40
48
|
"build": "node build.mjs",
|
|
41
|
-
"clean": "rm -rf dist"
|
|
49
|
+
"clean": "rm -rf dist",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:watch": "vitest"
|
|
42
52
|
}
|
|
43
53
|
}
|