@juit/vue-i18n 0.1.1 → 0.1.2
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/index.js +42 -42
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import { shallowRef as
|
|
1
|
+
import { shallowRef as T, computed as z, reactive as F, warn as h, inject as I } from "vue";
|
|
2
2
|
function L(e) {
|
|
3
|
-
const
|
|
3
|
+
const n = typeof e.defaultLanguage == "string" ? new Intl.Locale(e.defaultLanguage) : e.defaultLanguage, a = n.region ? `${n.language}-${n.region}` : n.language, o = e.translations ? structuredClone(e.translations) : {}, {
|
|
4
4
|
dateOnlyFormat: i = { dateStyle: "medium" },
|
|
5
5
|
timeOnlyFormat: g = { timeStyle: "medium" },
|
|
6
6
|
dateTimeFormat: u = { dateStyle: "medium", timeStyle: "medium" },
|
|
7
7
|
numberFormat: d = {}
|
|
8
|
-
} = e.formats || {}, l =
|
|
9
|
-
const { language:
|
|
10
|
-
return r && c.unshift(`${
|
|
8
|
+
} = e.formats || {}, l = T(new Intl.Locale(a)), m = z(() => {
|
|
9
|
+
const { language: t, region: r } = l.value, c = [t];
|
|
10
|
+
return r && c.unshift(`${t}-${r}`), t !== a && c.push(a), c;
|
|
11
11
|
});
|
|
12
|
-
function f(
|
|
13
|
-
if (
|
|
14
|
-
const c =
|
|
12
|
+
function f(t, r = u) {
|
|
13
|
+
if (t == null || t === "") return "";
|
|
14
|
+
const c = t instanceof Date ? t : new Date(t), y = typeof r == "string" ? { dateStyle: r, timeStyle: r } : r;
|
|
15
15
|
return new Intl.DateTimeFormat(l.value, y).format(c);
|
|
16
16
|
}
|
|
17
|
-
function p(
|
|
18
|
-
return f(
|
|
17
|
+
function p(t, r = i) {
|
|
18
|
+
return f(t, typeof r == "string" ? { dateStyle: r } : r);
|
|
19
19
|
}
|
|
20
|
-
function $(
|
|
21
|
-
return f(
|
|
20
|
+
function $(t, r = g) {
|
|
21
|
+
return f(t, typeof r == "string" ? { timeStyle: r } : r);
|
|
22
22
|
}
|
|
23
23
|
const s = {
|
|
24
24
|
get locale() {
|
|
25
25
|
return l.value;
|
|
26
26
|
},
|
|
27
|
-
set locale(
|
|
28
|
-
l.value =
|
|
27
|
+
set locale(t) {
|
|
28
|
+
l.value = t;
|
|
29
29
|
},
|
|
30
30
|
get language() {
|
|
31
31
|
return s.locale.language;
|
|
32
32
|
},
|
|
33
|
-
set language(
|
|
34
|
-
s.locale = new Intl.Locale(
|
|
33
|
+
set language(t) {
|
|
34
|
+
s.locale = new Intl.Locale(t, { ...l.value });
|
|
35
35
|
},
|
|
36
36
|
get region() {
|
|
37
37
|
return s.locale.region;
|
|
38
38
|
},
|
|
39
|
-
set region(
|
|
40
|
-
s.locale = new Intl.Locale(s.language, { ...s.locale, region:
|
|
39
|
+
set region(t) {
|
|
40
|
+
s.locale = new Intl.Locale(s.language, { ...s.locale, region: t || void 0 });
|
|
41
41
|
},
|
|
42
|
-
n(
|
|
43
|
-
if (
|
|
42
|
+
n(t, r) {
|
|
43
|
+
if (t == null) return "";
|
|
44
44
|
if (typeof r == "string") {
|
|
45
45
|
const c = r;
|
|
46
|
-
return new Intl.NumberFormat(s.locale, { style: "currency", currency: c }).format(
|
|
46
|
+
return new Intl.NumberFormat(s.locale, { style: "currency", currency: c }).format(t);
|
|
47
47
|
} else {
|
|
48
48
|
const c = r || d;
|
|
49
|
-
return new Intl.NumberFormat(s.locale, c).format(
|
|
49
|
+
return new Intl.NumberFormat(s.locale, c).format(t);
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
-
t(
|
|
53
|
-
return s.tc(
|
|
52
|
+
t(t, r) {
|
|
53
|
+
return s.tc(t, 1, r);
|
|
54
54
|
},
|
|
55
|
-
tc(
|
|
56
|
-
const y = j(o,
|
|
57
|
-
return N(y, Object.assign({ n: r }, c),
|
|
55
|
+
tc(t, r, c) {
|
|
56
|
+
const y = j(o, t, m.value), v = new Intl.NumberFormat(s.locale, d);
|
|
57
|
+
return N(y, Object.assign({ n: r }, c), v);
|
|
58
58
|
},
|
|
59
59
|
d: Object.assign(f, { date: p, time: $ })
|
|
60
60
|
};
|
|
61
61
|
return F(s);
|
|
62
62
|
}
|
|
63
63
|
const w = /* @__PURE__ */ new WeakMap();
|
|
64
|
-
function j(e,
|
|
65
|
-
if (!
|
|
66
|
-
if (typeof
|
|
64
|
+
function j(e, n, a) {
|
|
65
|
+
if (!n) throw new Error("No translation key specified");
|
|
66
|
+
if (typeof n == "string") {
|
|
67
67
|
let o = w.get(e);
|
|
68
68
|
o || w.set(e, o = {});
|
|
69
69
|
let i = o[a[0]];
|
|
70
70
|
i || (o[a[0]] = i = {});
|
|
71
|
-
let g = i[
|
|
71
|
+
let g = i[n];
|
|
72
72
|
if (!g) {
|
|
73
|
-
let u = e[
|
|
74
|
-
u || (h(`Translation key "${
|
|
73
|
+
let u = e[n];
|
|
74
|
+
u || (h(`Translation key "${n}" not found`), u = { [a[a.length - 1]]: n }), g = b(u, a), i[n] = g;
|
|
75
75
|
}
|
|
76
76
|
return g;
|
|
77
77
|
} else
|
|
78
|
-
return b(
|
|
78
|
+
return b(n, a);
|
|
79
79
|
}
|
|
80
|
-
function b(e,
|
|
80
|
+
function b(e, n) {
|
|
81
81
|
let a;
|
|
82
|
-
for (const l of
|
|
82
|
+
for (const l of n)
|
|
83
83
|
if (a = e[l], a) break;
|
|
84
84
|
if (!a) {
|
|
85
|
-
const l =
|
|
85
|
+
const l = n[n.length - 1];
|
|
86
86
|
return h(`Translation missing default language "${l}" in`, e), { zero: "", singular: "", plural: "" };
|
|
87
87
|
}
|
|
88
88
|
let o;
|
|
@@ -100,18 +100,18 @@ function b(e, t) {
|
|
|
100
100
|
const { zero: g, singular: u, plural: d } = o;
|
|
101
101
|
return { zero: g.trim(), singular: u.trim(), plural: d.trim() };
|
|
102
102
|
}
|
|
103
|
-
function N(e,
|
|
104
|
-
const o = typeof
|
|
103
|
+
function N(e, n, a) {
|
|
104
|
+
const o = typeof n.n == "string" ? Number(n.n) : n.n;
|
|
105
105
|
let i = o === 0 ? e.zero : o === 1 ? e.singular : e.plural;
|
|
106
|
-
for (const [g, u] of Object.entries(
|
|
106
|
+
for (const [g, u] of Object.entries(n)) {
|
|
107
107
|
const d = typeof u == "number" ? a.format(u) : typeof u == "string" ? u : u ? String(u) : "", l = new RegExp(`(.|^)({\\s*${g}\\s*})`, "gi");
|
|
108
108
|
i = i.replaceAll(l, (m, f, p) => f === "\\" ? p : f + d);
|
|
109
109
|
}
|
|
110
110
|
return i.trim();
|
|
111
111
|
}
|
|
112
112
|
const S = Symbol.for("@juit/vue-i18n/translator");
|
|
113
|
-
function P(e,
|
|
114
|
-
const o = L(typeof
|
|
113
|
+
function P(e, n) {
|
|
114
|
+
const o = L(typeof n == "string" ? { defaultLanguage: n } : n);
|
|
115
115
|
return e.config.globalProperties.$t = o.t, e.config.globalProperties.$tc = o.tc, e.config.globalProperties.$n = o.n, e.config.globalProperties.$d = o.d, e.provide(S, o), e;
|
|
116
116
|
}
|
|
117
117
|
function x() {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../lib/translator.ts","../lib/index.ts"],"sourcesContent":["import { computed, reactive, shallowRef, warn } from 'vue'\n\nimport type { DateTimeFormats, I18nOptions, ISOCountry, Translation, TranslationKey } from './index'\nimport type { ISOLanguage } from './iso-639'\n\n/* ===== TRANSLATOR INTERFACE =============================================== */\n\n/**\n * Parameters for the formatting of a translation.\n *\n * This type is used to pass parameters to the `t` and `tc` methods of the\n * translator, allowing for the interpolation of values into the translated\n * message.\n *\n * When the parameter value is a number, it will be formatted using the `n`\n * formatter before being interpolated into the message.\n */\nexport interface TranslationParams {\n [ key: string ]: string | number\n}\n\n/**\n * The date input type for date and time translation\n *\n * When the input is a non-empty `string`, or a `number`, it will be constructed\n * into a `Date` object before being formatted.\n *\n * When the input is `null`, `undefined`, or an empty string, formatted result\n * will be a simple empty string.\n */\nexport type DateInput = Date | string | number | null | undefined\n\n/**\n * The translator interface for the application.\n *\n * This interface provides methods to translate messages, format numbers, and\n * format dates and times.\n *\n * Configured instances can be accessed using the `useI18n()` composition\n * function, which will provide an instance of the translator.\n */\nexport interface Translator {\n /** The current ISO-639-1 language code used by this translator. */\n language: ISOLanguage\n /** The region (if any) used by thus translator to localize translations. */\n region: ISOCountry | undefined\n /** The `Locale` used by this translator (merges `language` and `region`) */\n locale: Intl.Locale\n\n /**\n * Return the (possibly parameterized) translation for the specified message\n * in the current language.\n *\n * Internally, this method uses the `tc(...)` function with `n=1`, in order to\n * avoid duplication of message keys\n */\n t(key: TranslationKey | Translation, params?: TranslationParams): string\n\n /**\n * Return the (possibly parameterized) translation for the specified message\n * in the current language, with pluralization.\n *\n * For pluralization, translation messages should be separated by the pipe\n * character, like in Vue I18N. Example:\n *\n * * `\" one apple | {n} apples \"` when _two_ translations are separated by a\n * pipe, the first will be used for singular, the second for zero or plural\n * * `\" no apples | one apple | {n} apples \"` when _three_ translations are\n * separated by a pipe, the first will be used for zero, the second for\n * singular, the second for zero or plural\n *\n * For convenience, the `{n}` message parameter will always be contextualized\n * with the number, unless overridden in the `params` themselves.\n */\n tc(key: TranslationKey | Translation, n: number, params?: TranslationParams): string\n\n /**\n * Format a number into a string according to the current language.\n *\n * If the `currency` parameter is set, the number will be formatted as a\n * currency value, using the specified currency symbol.\n */\n n(value?: number | bigint | null | undefined, currency?: string): string\n\n /**\n * Format a number into a string according to the current language.\n *\n * When `format` is provided, it will be used to configure the number format,\n * otherwise the default `format.numberFormat` plugin option will be used.\n */\n n(value?: number | bigint | null | undefined, format?: Intl.NumberFormatOptions): string\n\n d: {\n /**\n * Format date and time according to the current language.\n *\n * When `style` is provided, it will be used to configure the date and time\n * format, otherwise the default `format.dateTimeFormat` plugin option will\n * be used.\n */\n (date?: DateInput, style?: DateTimeFormats['dateTimeFormat']): string\n /**\n * Format the date part (without time) according to the current language.\n *\n * When `style` is provided, it will be used to configure the date and time\n * format, otherwise the default `format.dateOnlyFormat` plugin option will\n * be used.\n */\n date(date?: DateInput, style?: DateTimeFormats['dateOnlyFormat']): string\n /**\n * Format the time part (without date) according to the current language.\n *\n * When `style` is provided, it will be used to configure the date and time\n * format, otherwise the default `format.timeOnlyFormat` plugin option will\n * be used.\n */\n time(date?: DateInput, style?: DateTimeFormats['timeOnlyFormat']): string\n }\n}\n\n/* ===== TRANSLATOR IMPLEMENTATION ========================================== */\n\n/** Create a _reactive_ translator object from the given options */\nexport function makeTranslator(options: I18nOptions): Translator {\n // Default locale, parsing the default language\n const defaultLocale: Intl.Locale = typeof options.defaultLanguage === 'string' ?\n new Intl.Locale(options.defaultLanguage) :\n options.defaultLanguage\n\n // Normalized default language (language-REGION)\n const defaultLanguage = defaultLocale.region ?\n `${defaultLocale.language}-${defaultLocale.region}` :\n defaultLocale.language\n\n const translations: InternalTranslations = options.translations ? structuredClone(options.translations) : {}\n const {\n dateOnlyFormat = { dateStyle: 'medium' },\n timeOnlyFormat = { timeStyle: 'medium' },\n dateTimeFormat = { dateStyle: 'medium', timeStyle: 'medium' },\n numberFormat = {},\n } = options.formats || {}\n\n // Current locale, from the browser's language settings\n const locale = shallowRef(new Intl.Locale(defaultLanguage))\n\n // Language order, from the current locale\n const languages = computed(() => {\n const { language, region } = locale.value\n const order: string[] = [ language ]\n if (region) order.unshift(`${language}-${region}`)\n if (language !== defaultLanguage) order.push(defaultLanguage)\n return order as any as LanguageKeys\n })\n\n // Date-time translator, from the current locale\n function dateTime(input?: DateInput, style = dateTimeFormat): string {\n if (input == null) return ''\n\n const date = input instanceof Date ? input : new Date(input)\n const options = typeof style === 'string' ? { dateStyle: style, timeStyle: style } : style\n return new Intl.DateTimeFormat(locale.value, options).format(date)\n }\n\n // Date-only translator, from the current locale\n function dateOnly(input?: DateInput, style = dateOnlyFormat): string {\n const options = typeof style === 'string' ? { dateStyle: style } : style\n return dateTime(input, options)\n }\n\n // Time-only translator, from the current locale\n function timeOnly(input?: DateInput, style = timeOnlyFormat): string {\n const options = typeof style === 'string' ? { timeStyle: style } : style\n return dateTime(input, options)\n }\n\n // The translator object (non-reactive)\n const translator = {\n get locale() {\n return locale.value\n },\n\n set locale(value: Intl.Locale) {\n locale.value = value\n },\n\n get language(): ISOLanguage {\n return translator.locale.language as ISOLanguage\n },\n\n set language(value: ISOLanguage) {\n translator.locale = new Intl.Locale(value, { ...locale.value })\n },\n\n get region(): ISOCountry | undefined {\n return translator.locale.region as ISOCountry\n },\n\n set region(value: ISOCountry | undefined) {\n translator.locale = new Intl.Locale(translator.language, { ...translator.locale, region: value || undefined })\n },\n\n n(value?: number | bigint | null | undefined, currencyOrOptions?: string | Intl.NumberFormatOptions): string {\n if (value == null) return '' // null or undefined produces an empty string\n if (typeof currencyOrOptions === 'string') {\n const currency = currencyOrOptions\n return new Intl.NumberFormat(translator.locale, { style: 'currency', currency }).format(value)\n } else {\n const options = currencyOrOptions || numberFormat\n return new Intl.NumberFormat(translator.locale, options).format(value)\n }\n },\n\n t(translation: TranslationKey | Translation, params?: TranslationParams): string {\n return translator.tc(translation, 1, params)\n },\n\n tc(translation: TranslationKey | Translation, n: number, params?: TranslationParams): string {\n const template = getTemplate(translations, translation, languages.value)\n const format = new Intl.NumberFormat(translator.locale, numberFormat)\n return replaceParams(template, Object.assign({ n }, params), format)\n },\n\n d: Object.assign(dateTime, { date: dateOnly, time: timeOnly }),\n } as const satisfies Translator\n\n // Return a reactive version of the translator\n return reactive(translator)\n}\n\n/* ===== TRANSLATION UTILITIES ============================================== */\n\ntype LanguageKeys = readonly [ string, ...string[] ]\ntype InternalTranslation = Record<string, string | undefined>\ntype InternalTranslations = Record<string, InternalTranslation>\ntype TranslationTemplate = { zero: string, singular: string, plural: string }\n\n/**\n * Cached parsed translation templates.\n *\n * The keys are:\n * 1) the translations instance (WeakMap key)\n * 2) the current language (first entry in the order)\n * 3) the translation key\n */\nconst caches = new WeakMap<InternalTranslations, Record<string, Record<string, TranslationTemplate>>>()\n\n/** Get the `TranslationTemplate` for the translation or translation key. */\nfunction getTemplate(\n translations: InternalTranslations,\n translation: TranslationKey | Translation,\n languages: LanguageKeys,\n): TranslationTemplate {\n if (! translation) throw new Error('No translation key specified')\n\n if (typeof translation === 'string') {\n // Get the cache for the messages instance\n let cache = caches.get(translations)\n if (! cache) caches.set(translations, cache = {})\n\n // Get the cache for the current language\n let languageCache = cache[languages[0]]\n if (! languageCache) cache[languages[0]] = languageCache = {}\n\n // Get the translation from the cache or parse it\n let template = languageCache[translation]\n if (! template) {\n let object = translations[translation]\n if (! object) {\n warn(`Translation key \"${translation}\" not found`)\n object = { [languages[languages.length - 1]!]: translation }\n }\n template = extractTemplate(object, languages)\n languageCache[translation] = template\n }\n\n return template\n } else {\n return extractTemplate(translation, languages)\n }\n}\n\n/**\n * Extract a message from a translation, according to its locale, and split\n * it into its parsed components: zero, singular, and plural.\n */\nfunction extractTemplate(\n translation: InternalTranslation,\n languages: LanguageKeys,\n): TranslationTemplate {\n let string: string | undefined = undefined\n\n for (const language of languages) {\n string = translation[language as any]\n if (string) break\n }\n\n if (! string) {\n const language = languages[languages.length - 1]\n warn(`Translation missing default language \"${language}\" in`, translation)\n return { zero: '', singular: '', plural: '' }\n }\n\n let parsed: TranslationTemplate\n\n const translations = string.split(/(?<!\\\\)(?:\\\\\\\\)*\\|/)\n if (translations.length === 1) {\n const [ singular ] = translations\n parsed = { zero: singular!, singular: singular!, plural: singular! }\n } else if (translations.length === 2) {\n const [ singular, plural ] = translations\n parsed ={ zero: plural!, singular: singular!, plural: plural! }\n } else {\n const [ zero, singular, plural ] = translations\n parsed ={ zero: zero!, singular: singular!, plural: plural! }\n }\n\n const { zero, singular, plural } = parsed\n return { zero: zero.trim(), singular: singular.trim(), plural: plural.trim() }\n}\n\n/** Replace the parameters in a translation template, returning a string */\nfunction replaceParams(\n template: TranslationTemplate,\n params: TranslationParams,\n format: Intl.NumberFormat,\n): string {\n // Select the template to use based on the \"n\" (number) parameter\n const n = typeof params.n === 'string' ? Number(params.n) : params.n\n let formatted = n === 0 ? template.zero :\n n === 1 ? template.singular :\n template.plural\n\n // Replace any property `{ prop }` with the associated value\n for (const [ prop, value ] of Object.entries(params)) {\n const string =\n typeof value === 'number' ? format.format(value) :\n typeof value === 'string' ? value :\n value ? String(value) : ''\n\n // Expression matches `{ xxx }` where `{` is _not_ preceded by a '\\'\n const expr = new RegExp(`(.|^)({\\\\s*${prop}\\\\s*})`, 'gi')\n formatted = formatted.replaceAll(expr, (_, before, token) => {\n return before === '\\\\' ? token : before + string\n })\n }\n\n // All done!\n return formatted.trim()\n}\n","import { inject } from 'vue'\n\nimport { makeTranslator } from './translator'\n\nimport type { App } from 'vue'\nimport type { ISOLanguage } from './iso-639'\nimport type { Translator } from './translator'\n\n/* ===== REFERENCE LANGUAGES AND COUNTRIES ================================== */\n\nexport type * from './iso-3166'\nexport type * from './iso-639'\n\n/* ===== TYPES FOR DECLARATION MERGING ====================================== */\n\n/**\n * I18n Configuration interface (to be merged with the actual configuration).\n *\n * This interface (intentionally empty) is used to merge the actual per-app\n * configuration of the translation system, in order to provide the correct\n * types to the rest of the system.\n *\n * Two properties are expected to be defined in the configuration:\n *\n * * `languages`: the list of supported languages for the application. Those\n * are ISO 639-1 language codes, and when specified, _every_\n * translation _must_ include a translation for each.\n * * `translationKeys`: the list of translation keys known by the application.\n * Those are the arbitrary keys used to identify the\n * messages to be translated with the `t` and `tc`\n * methods of `Translator`.\n *\n * To configure the types, follow the example below:\n *\n * ```ts\n * const translations = {\n * 'hello': { en: 'Hello, world!', de: 'Hallo, Welt!' }\n * } as const satisfies TranslationsOptions\n *\n * declare module '@juit/vue-i18n' {\n * export interface I18nConfiguration {\n * translationKeys: keyof typeof translations\n * languages: 'de' | 'en'\n * }\n * }\n * ```\n */\nexport interface I18nConfiguration {\n // languages: 'de' | 'en'\n // translationKeys: 'hello'\n}\n\n/* ===== FROM CONFIG TO TRANSLATIONS ======================================== */\n\n/** Extract the value associated with key `K` from type `T` if it extends `R`, otherwise return `R` */\ntype ExtractConfig<T, R, K extends string> = T extends { [ X in K ]: infer V } ? V extends R ? V : R : R\n\n/** The languages configured in `I18nConfiguration` or all ISO languages */\nexport type Language = ExtractConfig<I18nConfiguration, ISOLanguage, 'languages'>\n\n/** Base translations, either required when languages are set or all optional */\ntype BaseTranslation = ISOLanguage extends Language ? {\n readonly [ key in ISOLanguage ]?: string\n} : {\n readonly [ key in Language ]: string\n}\n\n/** Extended translations, supporting multiple region of each language */\ntype ExtendedTranslation = {\n readonly [ key in `${Language}-${string}` ]?: string\n}\n\n/** Prettify our `Translations` exported type */\ntype PrettifyTranslation<T> = { [ l in keyof T ]: T[l] }\n\n/**\n * A type describing the translations for a given translation key.\n *\n * When the `I18nConfig` interface is properly merged with and its contains\n * the `languages` property, this type will represent the list of required\n * translation keys (languages) required for each translation.\n *\n * When left unconfigured, all ISO languages will be considered as optional.\n */\nexport type Translation = PrettifyTranslation<BaseTranslation & ExtendedTranslation>\n\n/**\n * All known translation keys.\n *\n * When the `I18nConfig` interface is properly merged with and its contains\n * the `translationKeys` property, this type will represent the list of\n * translations keys available to the `t(...)` and `tc(...)` methods.\n *\n * When left unconfigured, this type will be `string`.\n */\nexport type TranslationKey = ExtractConfig<I18nConfiguration, string, 'translationKeys'>\n\n/* ===== MODULE INITIALIZATION ============================================== */\n\n/* Export the translator types */\nexport type * from './translator'\n\n/**\n * Options to initialize the translations handled by the translation system.\n *\n * Shared translations are defined as a key-value pair, where the key is the\n * identifier of the translation, and the value is an object containing the\n * translations for each language.\n */\nexport interface Translations {\n readonly [ key: string ]: Translation\n}\n\n/**\n * Options to initialize the date and time formats used by the translation\n * system.\n *\n * * `dateTimeFormat`: the default format for times and dates used by `$d(...)`.\n * * `dateOnlyFormat`: the default format for dates used by `$d.date(...)`.\n * * `timeOnlyFormat`: the default format for times used by `$d.time(...)`.\n * * `numberFormat`: the default format for numbers used by `$n(...)`.\n */\nexport interface DateTimeFormats {\n dateOnlyFormat?: Intl.DateTimeFormatOptions | Intl.DateTimeFormatOptions['dateStyle']\n timeOnlyFormat?: Intl.DateTimeFormatOptions | Intl.DateTimeFormatOptions['timeStyle']\n dateTimeFormat?: Intl.DateTimeFormatOptions | (Intl.DateTimeFormatOptions['dateStyle'] & Intl.DateTimeFormatOptions['timeStyle'])\n numberFormat?: Intl.NumberFormatOptions\n}\n\n/** The language or locale to use at construction */\nexport type DefaultLanguage = ISOLanguage | `${ISOLanguage}-${string}` | Intl.Locale\n\n/** Options to initialize the I18n plugin */\nexport interface I18nOptions {\n defaultLanguage: DefaultLanguage,\n translations?: Translations,\n formats?: DateTimeFormats,\n}\n\n/* ===== PUBLIC METHODS ===================================================== */\n\n/** Symbol for Vue injections */\nconst injectionSymbol = Symbol.for('@juit/vue-i18n/translator')\n\n/** Initialize the translation system plugin */\nexport function i18n(app: App, optionsOrLanguage: Language | I18nOptions): App {\n const options = typeof optionsOrLanguage === 'string' ?\n { defaultLanguage: optionsOrLanguage } : optionsOrLanguage\n\n const translator = makeTranslator(options)\n\n app.config.globalProperties.$t = translator.t\n app.config.globalProperties.$tc = translator.tc\n app.config.globalProperties.$n = translator.n\n app.config.globalProperties.$d = translator.d\n\n app.provide(injectionSymbol, translator)\n return app\n}\n\n/** Retrieve the translator instance from the Vue app */\nexport function useTranslator(): Translator {\n const translator = inject(injectionSymbol)\n if (! translator) throw new Error('No translator found in the Vue app')\n return translator as Translator\n}\n\n/* ===== VUE EXTENSIONS ===================================================== */\n\n// Extension to the Vue component interface\ndeclare module 'vue' {\n interface ComponentCustomProperties {\n /** Translate a message according to the current language */\n $t: Translator['t']\n /**\n * Return the (possibly parameterized) translation for the specified message\n * in the current language, with pluralization.\n */\n $tc: Translator['tc']\n /** Format a number into a string according to the current language */\n $n: Translator['n']\n /**\n * Format date and time using the specified style (defaults to `medium`)\n * according to the current language\n */\n $d: Translator['d']\n }\n}\n"],"names":["makeTranslator","options","defaultLocale","defaultLanguage","translations","dateOnlyFormat","timeOnlyFormat","dateTimeFormat","numberFormat","locale","shallowRef","languages","computed","language","region","order","dateTime","input","style","date","dateOnly","timeOnly","translator","value","currencyOrOptions","currency","translation","params","n","template","getTemplate","format","replaceParams","reactive","caches","cache","languageCache","object","warn","extractTemplate","string","parsed","singular","plural","zero","formatted","prop","expr","_","before","token","injectionSymbol","i18n","app","optionsOrLanguage","useTranslator","inject"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../lib/translator.ts","../lib/index.ts"],"sourcesContent":["import { computed, reactive, shallowRef, warn } from 'vue'\n\nimport type { DateTimeFormats, I18nOptions, ISOCountry, Translation, TranslationKey } from './index'\nimport type { ISOLanguage } from './iso-639'\n\n/* ===== TRANSLATOR INTERFACE =============================================== */\n\n/**\n * Parameters for the formatting of a translation.\n *\n * This type is used to pass parameters to the `t` and `tc` methods of the\n * translator, allowing for the interpolation of values into the translated\n * message.\n *\n * When the parameter value is a number, it will be formatted using the `n`\n * formatter before being interpolated into the message.\n */\nexport interface TranslationParams {\n [ key: string ]: string | number\n}\n\n/**\n * The date input type for date and time translation\n *\n * When the input is a non-empty `string`, or a `number`, it will be constructed\n * into a `Date` object before being formatted.\n *\n * When the input is `null`, `undefined`, or an empty string, formatted result\n * will be a simple empty string.\n */\nexport type DateInput = Date | string | number | null | undefined\n\n/**\n * The translator interface for the application.\n *\n * This interface provides methods to translate messages, format numbers, and\n * format dates and times.\n *\n * Configured instances can be accessed using the `useI18n()` composition\n * function, which will provide an instance of the translator.\n */\nexport interface Translator {\n /** The current ISO-639-1 language code used by this translator. */\n language: ISOLanguage\n /** The region (if any) used by thus translator to localize translations. */\n region: ISOCountry | undefined\n /** The `Locale` used by this translator (merges `language` and `region`) */\n locale: Intl.Locale\n\n /**\n * Return the (possibly parameterized) translation for the specified message\n * in the current language.\n *\n * Internally, this method uses the `tc(...)` function with `n=1`, in order to\n * avoid duplication of message keys\n */\n t(key: TranslationKey | Translation, params?: TranslationParams): string\n\n /**\n * Return the (possibly parameterized) translation for the specified message\n * in the current language, with pluralization.\n *\n * For pluralization, translation messages should be separated by the pipe\n * character, like in Vue I18N. Example:\n *\n * * `\" one apple | {n} apples \"` when _two_ translations are separated by a\n * pipe, the first will be used for singular, the second for zero or plural\n * * `\" no apples | one apple | {n} apples \"` when _three_ translations are\n * separated by a pipe, the first will be used for zero, the second for\n * singular, the second for zero or plural\n *\n * For convenience, the `{n}` message parameter will always be contextualized\n * with the number, unless overridden in the `params` themselves.\n */\n tc(key: TranslationKey | Translation, n: number, params?: TranslationParams): string\n\n /**\n * Format a number into a string according to the current language.\n *\n * If the `currency` parameter is set, the number will be formatted as a\n * currency value, using the specified currency symbol.\n */\n n(value?: number | bigint | null | undefined, currency?: string): string\n\n /**\n * Format a number into a string according to the current language.\n *\n * When `format` is provided, it will be used to configure the number format,\n * otherwise the default `format.numberFormat` plugin option will be used.\n */\n n(value?: number | bigint | null | undefined, format?: Intl.NumberFormatOptions): string\n\n d: {\n /**\n * Format date and time according to the current language.\n *\n * When `style` is provided, it will be used to configure the date and time\n * format, otherwise the default `format.dateTimeFormat` plugin option will\n * be used.\n */\n (date?: DateInput, style?: DateTimeFormats['dateTimeFormat']): string\n /**\n * Format the date part (without time) according to the current language.\n *\n * When `style` is provided, it will be used to configure the date and time\n * format, otherwise the default `format.dateOnlyFormat` plugin option will\n * be used.\n */\n date(date?: DateInput, style?: DateTimeFormats['dateOnlyFormat']): string\n /**\n * Format the time part (without date) according to the current language.\n *\n * When `style` is provided, it will be used to configure the date and time\n * format, otherwise the default `format.timeOnlyFormat` plugin option will\n * be used.\n */\n time(date?: DateInput, style?: DateTimeFormats['timeOnlyFormat']): string\n }\n}\n\n/* ===== TRANSLATOR IMPLEMENTATION ========================================== */\n\n/** Create a _reactive_ translator object from the given options */\nexport function makeTranslator(options: I18nOptions): Translator {\n // Default locale, parsing the default language\n const defaultLocale: Intl.Locale = typeof options.defaultLanguage === 'string' ?\n new Intl.Locale(options.defaultLanguage) :\n options.defaultLanguage\n\n // Normalized default language (language-REGION)\n const defaultLanguage = defaultLocale.region ?\n `${defaultLocale.language}-${defaultLocale.region}` :\n defaultLocale.language\n\n const translations: InternalTranslations = options.translations ? structuredClone(options.translations) : {}\n const {\n dateOnlyFormat = { dateStyle: 'medium' },\n timeOnlyFormat = { timeStyle: 'medium' },\n dateTimeFormat = { dateStyle: 'medium', timeStyle: 'medium' },\n numberFormat = {},\n } = options.formats || {}\n\n // Current locale, from the browser's language settings\n const locale = shallowRef(new Intl.Locale(defaultLanguage))\n\n // Language order, from the current locale\n const languages = computed(() => {\n const { language, region } = locale.value\n const order: string[] = [ language ]\n if (region) order.unshift(`${language}-${region}`)\n if (language !== defaultLanguage) order.push(defaultLanguage)\n return order as any as LanguageKeys\n })\n\n // Date-time translator, from the current locale\n function dateTime(input?: DateInput, style = dateTimeFormat): string {\n if ((input == null) || (input === '')) return ''\n\n const date = input instanceof Date ? input : new Date(input)\n const options = typeof style === 'string' ? { dateStyle: style, timeStyle: style } : style\n return new Intl.DateTimeFormat(locale.value, options).format(date)\n }\n\n // Date-only translator, from the current locale\n function dateOnly(input?: DateInput, style = dateOnlyFormat): string {\n const options = typeof style === 'string' ? { dateStyle: style } : style\n return dateTime(input, options)\n }\n\n // Time-only translator, from the current locale\n function timeOnly(input?: DateInput, style = timeOnlyFormat): string {\n const options = typeof style === 'string' ? { timeStyle: style } : style\n return dateTime(input, options)\n }\n\n // The translator object (non-reactive)\n const translator = {\n get locale() {\n return locale.value\n },\n\n set locale(value: Intl.Locale) {\n locale.value = value\n },\n\n get language(): ISOLanguage {\n return translator.locale.language as ISOLanguage\n },\n\n set language(value: ISOLanguage) {\n translator.locale = new Intl.Locale(value, { ...locale.value })\n },\n\n get region(): ISOCountry | undefined {\n return translator.locale.region as ISOCountry\n },\n\n set region(value: ISOCountry | undefined) {\n translator.locale = new Intl.Locale(translator.language, { ...translator.locale, region: value || undefined })\n },\n\n n(value?: number | bigint | null | undefined, currencyOrOptions?: string | Intl.NumberFormatOptions): string {\n if (value == null) return '' // null or undefined produces an empty string\n if (typeof currencyOrOptions === 'string') {\n const currency = currencyOrOptions\n return new Intl.NumberFormat(translator.locale, { style: 'currency', currency }).format(value)\n } else {\n const options = currencyOrOptions || numberFormat\n return new Intl.NumberFormat(translator.locale, options).format(value)\n }\n },\n\n t(translation: TranslationKey | Translation, params?: TranslationParams): string {\n return translator.tc(translation, 1, params)\n },\n\n tc(translation: TranslationKey | Translation, n: number, params?: TranslationParams): string {\n const template = getTemplate(translations, translation, languages.value)\n const format = new Intl.NumberFormat(translator.locale, numberFormat)\n return replaceParams(template, Object.assign({ n }, params), format)\n },\n\n d: Object.assign(dateTime, { date: dateOnly, time: timeOnly }),\n } as const satisfies Translator\n\n // Return a reactive version of the translator\n return reactive(translator)\n}\n\n/* ===== TRANSLATION UTILITIES ============================================== */\n\ntype LanguageKeys = readonly [ string, ...string[] ]\ntype InternalTranslation = Record<string, string | undefined>\ntype InternalTranslations = Record<string, InternalTranslation>\ntype TranslationTemplate = { zero: string, singular: string, plural: string }\n\n/**\n * Cached parsed translation templates.\n *\n * The keys are:\n * 1) the translations instance (WeakMap key)\n * 2) the current language (first entry in the order)\n * 3) the translation key\n */\nconst caches = new WeakMap<InternalTranslations, Record<string, Record<string, TranslationTemplate>>>()\n\n/** Get the `TranslationTemplate` for the translation or translation key. */\nfunction getTemplate(\n translations: InternalTranslations,\n translation: TranslationKey | Translation,\n languages: LanguageKeys,\n): TranslationTemplate {\n if (! translation) throw new Error('No translation key specified')\n\n if (typeof translation === 'string') {\n // Get the cache for the messages instance\n let cache = caches.get(translations)\n if (! cache) caches.set(translations, cache = {})\n\n // Get the cache for the current language\n let languageCache = cache[languages[0]]\n if (! languageCache) cache[languages[0]] = languageCache = {}\n\n // Get the translation from the cache or parse it\n let template = languageCache[translation]\n if (! template) {\n let object = translations[translation]\n if (! object) {\n warn(`Translation key \"${translation}\" not found`)\n object = { [languages[languages.length - 1]!]: translation }\n }\n template = extractTemplate(object, languages)\n languageCache[translation] = template\n }\n\n return template\n } else {\n return extractTemplate(translation, languages)\n }\n}\n\n/**\n * Extract a message from a translation, according to its locale, and split\n * it into its parsed components: zero, singular, and plural.\n */\nfunction extractTemplate(\n translation: InternalTranslation,\n languages: LanguageKeys,\n): TranslationTemplate {\n let string: string | undefined = undefined\n\n for (const language of languages) {\n string = translation[language as any]\n if (string) break\n }\n\n if (! string) {\n const language = languages[languages.length - 1]\n warn(`Translation missing default language \"${language}\" in`, translation)\n return { zero: '', singular: '', plural: '' }\n }\n\n let parsed: TranslationTemplate\n\n const translations = string.split(/(?<!\\\\)(?:\\\\\\\\)*\\|/)\n if (translations.length === 1) {\n const [ singular ] = translations\n parsed = { zero: singular!, singular: singular!, plural: singular! }\n } else if (translations.length === 2) {\n const [ singular, plural ] = translations\n parsed ={ zero: plural!, singular: singular!, plural: plural! }\n } else {\n const [ zero, singular, plural ] = translations\n parsed ={ zero: zero!, singular: singular!, plural: plural! }\n }\n\n const { zero, singular, plural } = parsed\n return { zero: zero.trim(), singular: singular.trim(), plural: plural.trim() }\n}\n\n/** Replace the parameters in a translation template, returning a string */\nfunction replaceParams(\n template: TranslationTemplate,\n params: TranslationParams,\n format: Intl.NumberFormat,\n): string {\n // Select the template to use based on the \"n\" (number) parameter\n const n = typeof params.n === 'string' ? Number(params.n) : params.n\n let formatted = n === 0 ? template.zero :\n n === 1 ? template.singular :\n template.plural\n\n // Replace any property `{ prop }` with the associated value\n for (const [ prop, value ] of Object.entries(params)) {\n const string =\n typeof value === 'number' ? format.format(value) :\n typeof value === 'string' ? value :\n value ? String(value) : ''\n\n // Expression matches `{ xxx }` where `{` is _not_ preceded by a '\\'\n const expr = new RegExp(`(.|^)({\\\\s*${prop}\\\\s*})`, 'gi')\n formatted = formatted.replaceAll(expr, (_, before, token) => {\n return before === '\\\\' ? token : before + string\n })\n }\n\n // All done!\n return formatted.trim()\n}\n","import { inject } from 'vue'\n\nimport { makeTranslator } from './translator'\n\nimport type { App } from 'vue'\nimport type { ISOLanguage } from './iso-639'\nimport type { Translator } from './translator'\n\n/* ===== REFERENCE LANGUAGES AND COUNTRIES ================================== */\n\nexport type * from './iso-3166'\nexport type * from './iso-639'\n\n/* ===== TYPES FOR DECLARATION MERGING ====================================== */\n\n/**\n * I18n Configuration interface (to be merged with the actual configuration).\n *\n * This interface (intentionally empty) is used to merge the actual per-app\n * configuration of the translation system, in order to provide the correct\n * types to the rest of the system.\n *\n * Two properties are expected to be defined in the configuration:\n *\n * * `languages`: the list of supported languages for the application. Those\n * are ISO 639-1 language codes, and when specified, _every_\n * translation _must_ include a translation for each.\n * * `translationKeys`: the list of translation keys known by the application.\n * Those are the arbitrary keys used to identify the\n * messages to be translated with the `t` and `tc`\n * methods of `Translator`.\n *\n * To configure the types, follow the example below:\n *\n * ```ts\n * const translations = {\n * 'hello': { en: 'Hello, world!', de: 'Hallo, Welt!' }\n * } as const satisfies TranslationsOptions\n *\n * declare module '@juit/vue-i18n' {\n * export interface I18nConfiguration {\n * translationKeys: keyof typeof translations\n * languages: 'de' | 'en'\n * }\n * }\n * ```\n */\nexport interface I18nConfiguration {\n // languages: 'de' | 'en'\n // translationKeys: 'hello'\n}\n\n/* ===== FROM CONFIG TO TRANSLATIONS ======================================== */\n\n/** Extract the value associated with key `K` from type `T` if it extends `R`, otherwise return `R` */\ntype ExtractConfig<T, R, K extends string> = T extends { [ X in K ]: infer V } ? V extends R ? V : R : R\n\n/** The languages configured in `I18nConfiguration` or all ISO languages */\nexport type Language = ExtractConfig<I18nConfiguration, ISOLanguage, 'languages'>\n\n/** Base translations, either required when languages are set or all optional */\ntype BaseTranslation = ISOLanguage extends Language ? {\n readonly [ key in ISOLanguage ]?: string\n} : {\n readonly [ key in Language ]: string\n}\n\n/** Extended translations, supporting multiple region of each language */\ntype ExtendedTranslation = {\n readonly [ key in `${Language}-${string}` ]?: string\n}\n\n/** Prettify our `Translations` exported type */\ntype PrettifyTranslation<T> = { [ l in keyof T ]: T[l] }\n\n/**\n * A type describing the translations for a given translation key.\n *\n * When the `I18nConfig` interface is properly merged with and its contains\n * the `languages` property, this type will represent the list of required\n * translation keys (languages) required for each translation.\n *\n * When left unconfigured, all ISO languages will be considered as optional.\n */\nexport type Translation = PrettifyTranslation<BaseTranslation & ExtendedTranslation>\n\n/**\n * All known translation keys.\n *\n * When the `I18nConfig` interface is properly merged with and its contains\n * the `translationKeys` property, this type will represent the list of\n * translations keys available to the `t(...)` and `tc(...)` methods.\n *\n * When left unconfigured, this type will be `string`.\n */\nexport type TranslationKey = ExtractConfig<I18nConfiguration, string, 'translationKeys'>\n\n/* ===== MODULE INITIALIZATION ============================================== */\n\n/* Export the translator types */\nexport type * from './translator'\n\n/**\n * Options to initialize the translations handled by the translation system.\n *\n * Shared translations are defined as a key-value pair, where the key is the\n * identifier of the translation, and the value is an object containing the\n * translations for each language.\n */\nexport interface Translations {\n readonly [ key: string ]: Translation\n}\n\n/**\n * Options to initialize the date and time formats used by the translation\n * system.\n *\n * * `dateTimeFormat`: the default format for times and dates used by `$d(...)`.\n * * `dateOnlyFormat`: the default format for dates used by `$d.date(...)`.\n * * `timeOnlyFormat`: the default format for times used by `$d.time(...)`.\n * * `numberFormat`: the default format for numbers used by `$n(...)`.\n */\nexport interface DateTimeFormats {\n dateOnlyFormat?: Intl.DateTimeFormatOptions | Intl.DateTimeFormatOptions['dateStyle']\n timeOnlyFormat?: Intl.DateTimeFormatOptions | Intl.DateTimeFormatOptions['timeStyle']\n dateTimeFormat?: Intl.DateTimeFormatOptions | (Intl.DateTimeFormatOptions['dateStyle'] & Intl.DateTimeFormatOptions['timeStyle'])\n numberFormat?: Intl.NumberFormatOptions\n}\n\n/** The language or locale to use at construction */\nexport type DefaultLanguage = ISOLanguage | `${ISOLanguage}-${string}` | Intl.Locale\n\n/** Options to initialize the I18n plugin */\nexport interface I18nOptions {\n defaultLanguage: DefaultLanguage,\n translations?: Translations,\n formats?: DateTimeFormats,\n}\n\n/* ===== PUBLIC METHODS ===================================================== */\n\n/** Symbol for Vue injections */\nconst injectionSymbol = Symbol.for('@juit/vue-i18n/translator')\n\n/** Initialize the translation system plugin */\nexport function i18n(app: App, optionsOrLanguage: Language | I18nOptions): App {\n const options = typeof optionsOrLanguage === 'string' ?\n { defaultLanguage: optionsOrLanguage } : optionsOrLanguage\n\n const translator = makeTranslator(options)\n\n app.config.globalProperties.$t = translator.t\n app.config.globalProperties.$tc = translator.tc\n app.config.globalProperties.$n = translator.n\n app.config.globalProperties.$d = translator.d\n\n app.provide(injectionSymbol, translator)\n return app\n}\n\n/** Retrieve the translator instance from the Vue app */\nexport function useTranslator(): Translator {\n const translator = inject(injectionSymbol)\n if (! translator) throw new Error('No translator found in the Vue app')\n return translator as Translator\n}\n\n/* ===== VUE EXTENSIONS ===================================================== */\n\n// Extension to the Vue component interface\ndeclare module 'vue' {\n interface ComponentCustomProperties {\n /** Translate a message according to the current language */\n $t: Translator['t']\n /**\n * Return the (possibly parameterized) translation for the specified message\n * in the current language, with pluralization.\n */\n $tc: Translator['tc']\n /** Format a number into a string according to the current language */\n $n: Translator['n']\n /**\n * Format date and time using the specified style (defaults to `medium`)\n * according to the current language\n */\n $d: Translator['d']\n }\n}\n"],"names":["makeTranslator","options","defaultLocale","defaultLanguage","translations","dateOnlyFormat","timeOnlyFormat","dateTimeFormat","numberFormat","locale","shallowRef","languages","computed","language","region","order","dateTime","input","style","date","dateOnly","timeOnly","translator","value","currencyOrOptions","currency","translation","params","n","template","getTemplate","format","replaceParams","reactive","caches","cache","languageCache","object","warn","extractTemplate","string","parsed","singular","plural","zero","formatted","prop","expr","_","before","token","injectionSymbol","i18n","app","optionsOrLanguage","useTranslator","inject"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juit/vue-i18n",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
"vue": "^3.5.13"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@plugjs/eslint-plugin": "^0.3.
|
|
21
|
+
"@plugjs/eslint-plugin": "^0.3.11",
|
|
22
22
|
"@types/node": "<21",
|
|
23
23
|
"@vitejs/plugin-vue": "^5.2.1",
|
|
24
24
|
"@vitest/coverage-v8": "^3.0.5",
|
|
25
25
|
"@vue/test-utils": "^2.4.6",
|
|
26
|
-
"eslint": "^9.
|
|
26
|
+
"eslint": "^9.20.0",
|
|
27
27
|
"eslint-plugin-vue": "^9.32.0",
|
|
28
28
|
"jsdom": "^26.0.0",
|
|
29
29
|
"typescript": "<5.7",
|