@rimelight/i18n 0.0.8 → 0.0.10

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/hono.d.mts CHANGED
@@ -3,6 +3,5 @@
3
3
  * Hono middleware for Rimelight i18n. Handles locale routing, redirection, currentLocale store
4
4
  * updates, and Cloudflare KV binding setup.
5
5
  */
6
- declare function i18n(): (c: any, next: any) => Promise<any>;
7
- //#endregion
8
- export { i18n };
6
+ export declare function i18n(): (c: any, next: any) => Promise<any>;
7
+ //#endregion
package/dist/hono.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { currentLocale } from "./runtime.mjs";
2
- import { setKVBinding } from "./index.mjs";
3
- import { defaultLocale, locales, prefixDefaultLocale } from "virtual:rimelight-i18n-config";
1
+ import { n as currentLocale } from "./runtime-LitYNVlO.mjs";
2
+ import { d as setKVBinding } from "./src-DDBWfigm.mjs";
4
3
  import { env } from "cloudflare:workers";
4
+ import { defaultLocale, locales, prefixDefaultLocale } from "virtual:rimelight-i18n-config";
5
5
  //#region src/hono.ts
6
6
  const SUPPORTED_LOCALES = new Set(locales);
7
7
  const DEFAULT_LOCALE = defaultLocale;
package/dist/index.d.mts CHANGED
@@ -1,28 +1,30 @@
1
- import { ComponentsJSON, CustomTranslations, KVNamespaceBinding, NestedTranslationKeys, TranslationKey } from "./types.mjs";
2
- import { clearCache, currentLocale, getFormatterInstance, getI18nInstance, getPluralCategory, initializeI18n, useFormat, useI18n, useI18nAsync } from "./runtime.mjs";
3
- import { RimelightI18nOptions, RimelightI18nPlugins, i18n } from "./plugin.mjs";
1
+ import { i as KVNamespaceBinding, n as CustomTranslations, o as NestedTranslationKeys, s as TranslationKey, t as ComponentsJSON } from "./types-BKUPwH-e.mjs";
2
+ import { a as getFormatterInstance, c as initializeI18n, d as useI18n, f as useI18nAsync, i as currentLocale, o as getI18nInstance, r as clearCache, s as getPluralCategory, u as useFormat } from "./runtime-Bc8I8sA4.mjs";
3
+ import { i as i18n, n as RimelightI18nPlugins, t as RimelightI18nOptions } from "./plugin-CFEm0e4U.mjs";
4
4
  import { TranslationLoader } from "@nanostores/i18n";
5
- import { defaultLocale, locales, prefixDefaultLocale } from "virtual:rimelight-i18n-config";
6
5
  //#region src/index.d.ts
6
+ declare let locales: string[];
7
+ declare let defaultLocale: string;
8
+ declare let prefixDefaultLocale: boolean;
7
9
  /**
8
10
  * Translates a single string key using dot-notation (e.g. `t("page_home.section_hero_title")`).
9
11
  * When augmented via CustomTranslations, keys are strictly type-checked and autocompleted.
10
12
  */
11
- declare function t<K extends string = TranslationKey>(key: K | TranslationKey, params?: Record<string, any>): string;
12
- declare function getLocale(): string;
13
+ export declare function t<K extends string = TranslationKey>(key: K | TranslationKey, params?: Record<string, any>): string;
14
+ export declare function getLocale(): string;
13
15
  /**
14
16
  * Returns a standard BCP 47 / RFC 5646 language tag suitable for HTML `<html lang="...">`. Maps
15
17
  * 2-letter codes like 'en' to 'en-US', 'pt' to 'pt-BR', 'es' to 'es-ES', etc.
16
18
  */
17
- declare function getHtmlLang(): string;
19
+ export declare function getHtmlLang(): string;
18
20
  /**
19
21
  * Returns an OpenGraph-compliant locale tag (e.g. 'en_US', 'pt_BR', 'es_ES').
20
22
  */
21
- declare function getOgLocale(): string;
22
- declare function getRelativeLocaleUrl(path: string): string;
23
- declare function getRelativeLocaleUrl(locale: string, path: string): string;
24
- declare function getRelativeLocaleUrlList(path?: string): string[];
25
- interface LanguageAlternate {
23
+ export declare function getOgLocale(): string;
24
+ export declare function getRelativeLocaleUrl(path: string): string;
25
+ export declare function getRelativeLocaleUrl(locale: string, path: string): string;
26
+ export declare function getRelativeLocaleUrlList(path?: string): string[];
27
+ export interface LanguageAlternate {
26
28
  hreflang: string;
27
29
  href: string;
28
30
  }
@@ -30,8 +32,8 @@ interface LanguageAlternate {
30
32
  * Generates an array of hreflang alternates for SEO tags (<link rel="alternate" hreflang="...">).
31
33
  * Automatically includes all supported locales and 'x-default'.
32
34
  */
33
- declare function getLanguageAlternates(pathname: string, siteUrl?: string): LanguageAlternate[];
34
- declare function setKVBinding(kv: KVNamespaceBinding): void;
35
- declare function createTranslationLoader(translations: Record<string, ComponentsJSON>): TranslationLoader;
35
+ export declare function getLanguageAlternates(pathname: string, siteUrl?: string): LanguageAlternate[];
36
+ export declare function setKVBinding(kv: KVNamespaceBinding): void;
37
+ export declare function createTranslationLoader(translations: Record<string, ComponentsJSON>): TranslationLoader;
36
38
  //#endregion
37
- export { type CustomTranslations, LanguageAlternate, type NestedTranslationKeys, type RimelightI18nOptions, type RimelightI18nPlugins, type TranslationKey, clearCache, createTranslationLoader, currentLocale, defaultLocale, getFormatterInstance, getHtmlLang, getI18nInstance, getLanguageAlternates, getLocale, getOgLocale, getPluralCategory, getRelativeLocaleUrl, getRelativeLocaleUrlList, i18n, i18n as rimelightI18n, initializeI18n, locales, prefixDefaultLocale, setKVBinding, t, useFormat, useI18n, useI18nAsync };
39
+ export { type CustomTranslations, type NestedTranslationKeys, type RimelightI18nOptions, type RimelightI18nPlugins, type TranslationKey, clearCache, currentLocale, defaultLocale, getFormatterInstance, getI18nInstance, getPluralCategory, i18n, i18n as rimelightI18n, initializeI18n, locales, prefixDefaultLocale, useFormat, useI18n, useI18nAsync };
package/dist/index.mjs CHANGED
@@ -1,110 +1,4 @@
1
- import { clearCache, currentLocale, getFormatterInstance, getI18nInstance, getPluralCategory, initializeI18n, t as t$1, useFormat, useI18n, useI18nAsync } from "./runtime.mjs";
2
- import { i18n } from "./plugin.mjs";
3
- import { defaultLocale, locales, prefixDefaultLocale, translations } from "virtual:rimelight-i18n-config";
4
- //#region src/index.ts
5
- if (translations && Object.keys(translations).length > 0) initializeI18n({
6
- defaultLocale: defaultLocale || "en",
7
- translations
8
- });
9
- /**
10
- * Translates a single string key using dot-notation (e.g. `t("page_home.section_hero_title")`).
11
- * When augmented via CustomTranslations, keys are strictly type-checked and autocompleted.
12
- */
13
- function t(key, params) {
14
- return t$1(key, params);
15
- }
16
- function getLocale() {
17
- return currentLocale.get() || defaultLocale || "en";
18
- }
19
- /**
20
- * Returns a standard BCP 47 / RFC 5646 language tag suitable for HTML `<html lang="...">`. Maps
21
- * 2-letter codes like 'en' to 'en-US', 'pt' to 'pt-BR', 'es' to 'es-ES', etc.
22
- */
23
- function getHtmlLang() {
24
- const loc = getLocale();
25
- return {
26
- en: "en-US",
27
- pt: "pt-BR",
28
- es: "es-ES"
29
- }[loc] || loc;
30
- }
31
- /**
32
- * Returns an OpenGraph-compliant locale tag (e.g. 'en_US', 'pt_BR', 'es_ES').
33
- */
34
- function getOgLocale() {
35
- const loc = getLocale();
36
- return {
37
- en: "en_US",
38
- pt: "pt_BR",
39
- es: "es_ES"
40
- }[loc] || `${loc}_${loc.toUpperCase()}`;
41
- }
42
- function getRelativeLocaleUrl(arg1, arg2) {
43
- let targetLocale;
44
- let targetPath;
45
- if (arg2 !== void 0) {
46
- targetLocale = arg1;
47
- targetPath = arg2;
48
- } else {
49
- targetLocale = getLocale();
50
- targetPath = arg1;
51
- }
52
- if (/^(?:[a-z]+:|\/\/|#)/i.test(targetPath) || targetPath.startsWith("mailto:") || targetPath.startsWith("tel:")) return targetPath;
53
- const [pathWithoutQueryAndHash, queryAndHash] = (() => {
54
- const match = targetPath.match(/^([^?#]*)([?#].*)?$/);
55
- return [match?.[1] ?? "", match?.[2] ?? ""];
56
- })();
57
- const rawCleanPath = pathWithoutQueryAndHash.replace(/^\/+/, "");
58
- const segments = rawCleanPath ? rawCleanPath.split("/") : [];
59
- if (segments.length > 0 && locales.includes(segments[0])) segments.shift();
60
- const cleanPath = segments.join("/");
61
- const prefix = prefixDefaultLocale || targetLocale !== defaultLocale ? `/${targetLocale}` : "";
62
- if (!cleanPath) return `${prefix ? `${prefix}/` : "/"}${queryAndHash}`;
63
- return `${`${prefix}/${cleanPath}${pathWithoutQueryAndHash.endsWith("/") ? "/" : ""}`}${queryAndHash}`;
64
- }
65
- function getRelativeLocaleUrlList(path = "/") {
66
- return locales.map((locale) => getRelativeLocaleUrl(locale, path));
67
- }
68
- /**
69
- * Generates an array of hreflang alternates for SEO tags (<link rel="alternate" hreflang="...">).
70
- * Automatically includes all supported locales and 'x-default'.
71
- */
72
- function getLanguageAlternates(pathname, siteUrl = "") {
73
- const normalizedSiteUrl = siteUrl.replace(/\/+$/, "");
74
- const alternates = locales.map((loc) => ({
75
- hreflang: loc,
76
- href: `${normalizedSiteUrl}${getRelativeLocaleUrl(loc, pathname)}`
77
- }));
78
- alternates.push({
79
- hreflang: "x-default",
80
- href: `${normalizedSiteUrl}${getRelativeLocaleUrl(defaultLocale, pathname)}`
81
- });
82
- return alternates;
83
- }
84
- let kvBinding = null;
85
- function setKVBinding(kv) {
86
- kvBinding = kv;
87
- }
88
- function createTranslationLoader(translations) {
89
- return async (locale, components) => {
90
- const localeTranslations = translations[locale] || {};
91
- if (!components || !Array.isArray(components) || components.length === 0) return localeTranslations;
92
- const activeKv = kvBinding;
93
- if (activeKv) try {
94
- const results = await Promise.all(components.map(async (name) => {
95
- const key = `locale:${locale}:${name}`;
96
- const data = await activeKv.get(key, "json");
97
- if (data == null) return { [name]: localeTranslations[name] ?? {} };
98
- return { [name]: data };
99
- }));
100
- return Object.assign({}, ...results);
101
- } catch {}
102
- const results = await Promise.all(components.map(async (name) => {
103
- const data = localeTranslations[name];
104
- return { [name]: data ?? {} };
105
- }));
106
- return Object.assign({}, ...results);
107
- };
108
- }
109
- //#endregion
1
+ import { a as getPluralCategory, c as useFormat, i as getI18nInstance, l as useI18n, n as currentLocale, o as initializeI18n, r as getFormatterInstance, t as clearCache, u as useI18nAsync } from "./runtime-LitYNVlO.mjs";
2
+ import { t as i18n } from "./plugin-jh44sjC0.mjs";
3
+ import { a as getLocale, c as getRelativeLocaleUrlList, d as setKVBinding, f as t, i as getLanguageAlternates, l as locales, n as defaultLocale, o as getOgLocale, r as getHtmlLang, s as getRelativeLocaleUrl, t as createTranslationLoader, u as prefixDefaultLocale } from "./src-DDBWfigm.mjs";
110
4
  export { clearCache, createTranslationLoader, currentLocale, defaultLocale, getFormatterInstance, getHtmlLang, getI18nInstance, getLanguageAlternates, getLocale, getOgLocale, getPluralCategory, getRelativeLocaleUrl, getRelativeLocaleUrlList, i18n, i18n as rimelightI18n, initializeI18n, locales, prefixDefaultLocale, setKVBinding, t, useFormat, useI18n, useI18nAsync };
@@ -0,0 +1,39 @@
1
+ //#region src/plugin.d.ts
2
+ interface RimelightI18nOptions {
3
+ /**
4
+ * List of supported locale codes (e.g. `["en", "pt"]`).
5
+ */
6
+ locales?: string[];
7
+ /**
8
+ * The default locale code (e.g. `"en"`).
9
+ */
10
+ defaultLocale?: string;
11
+ /**
12
+ * Whether to prefix the default locale in URLs.
13
+ *
14
+ * - `true` (default): all locales are prefixed — `/en/about`, `/pt/about`
15
+ * - `false`: the default locale has no prefix — `/about`, `/pt/about`
16
+ */
17
+ prefixDefaultLocale?: boolean;
18
+ validateExtraction?: boolean;
19
+ translations?: Record<string, Record<string, any>>;
20
+ kvBinding?: string;
21
+ translationLoader?: string;
22
+ }
23
+ interface RimelightI18nVitePlugin {
24
+ name: string;
25
+ enforce?: "pre" | "post";
26
+ resolveId?: (id: string) => string | null | undefined;
27
+ load?: (id: string) => string | null | undefined;
28
+ config?: (config: any) => any;
29
+ closeBundle?: () => Promise<void> | void;
30
+ [key: string]: any;
31
+ }
32
+ type RimelightI18nPlugins = RimelightI18nVitePlugin[];
33
+ /**
34
+ * Pure Vite plugin for Rimelight i18n. Provides virtual modules `@rimelight/i18n:runtime` and
35
+ * `virtual:rimelight-i18n-config`.
36
+ */
37
+ declare function i18n(options?: RimelightI18nOptions): RimelightI18nPlugins;
38
+ //#endregion
39
+ export { i18n as i, RimelightI18nPlugins as n, RimelightI18nVitePlugin as r, RimelightI18nOptions as t };
@@ -0,0 +1,55 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ //#region src/plugin.ts
4
+ /**
5
+ * Pure Vite plugin for Rimelight i18n. Provides virtual modules `@rimelight/i18n:runtime` and
6
+ * `virtual:rimelight-i18n-config`.
7
+ */
8
+ function i18n(options) {
9
+ const translations = options?.translations;
10
+ const kvBinding = options?.kvBinding ?? "TRANSLATIONS_KV";
11
+ const locales = options?.locales ?? ["en"];
12
+ const defaultLocale = options?.defaultLocale ?? "en";
13
+ const prefixDefaultLocale = options?.prefixDefaultLocale ?? true;
14
+ const normalisedTranslations = {};
15
+ if (translations) for (const [locale, val] of Object.entries(translations)) normalisedTranslations[locale] = val;
16
+ return [{
17
+ name: "vite-plugin-rimelight-i18n-config",
18
+ resolveId(id) {
19
+ if (id === "virtual:rimelight-i18n-config") return "\0" + id;
20
+ return null;
21
+ },
22
+ load(id) {
23
+ if (id === "\0virtual:rimelight-i18n-config") return `
24
+ export const locales = ${JSON.stringify(locales)};
25
+ export const defaultLocale = ${JSON.stringify(defaultLocale)};
26
+ export const prefixDefaultLocale = ${JSON.stringify(prefixDefaultLocale)};
27
+ export const translations = ${JSON.stringify(normalisedTranslations)};
28
+ `;
29
+ return null;
30
+ },
31
+ async closeBundle() {
32
+ if (!translations) return;
33
+ const seedData = { keys: [] };
34
+ for (const [locale, localeData] of Object.entries(translations)) for (const [component, componentData] of Object.entries(localeData)) seedData.keys.push({
35
+ key: `locale:${locale}:${component}`,
36
+ value: JSON.stringify(componentData)
37
+ });
38
+ const outDir = process.env["WRANGLER_OUT_DIR"] ?? "dist";
39
+ const seedFile = path.join(outDir, "_translations-seed.json");
40
+ try {
41
+ fs.mkdirSync(path.dirname(seedFile), { recursive: true });
42
+ fs.writeFileSync(seedFile, JSON.stringify(seedData, null, 2));
43
+ if (process.env["WRANGLER_SEED_KV"] === "true" || process.env["CI"] === "true") {
44
+ const { execSync } = await import("node:child_process");
45
+ execSync(`npx wrangler kv:bulk put --binding "${kvBinding}" "${seedFile}"`, {
46
+ stdio: "pipe",
47
+ timeout: 3e4
48
+ });
49
+ }
50
+ } catch {}
51
+ }
52
+ }];
53
+ }
54
+ //#endregion
55
+ export { i18n as t };
package/dist/plugin.d.mts CHANGED
@@ -1,39 +1,2 @@
1
- //#region src/plugin.d.ts
2
- interface RimelightI18nOptions {
3
- /**
4
- * List of supported locale codes (e.g. `["en", "pt"]`).
5
- */
6
- locales?: string[];
7
- /**
8
- * The default locale code (e.g. `"en"`).
9
- */
10
- defaultLocale?: string;
11
- /**
12
- * Whether to prefix the default locale in URLs.
13
- *
14
- * - `true` (default): all locales are prefixed — `/en/about`, `/pt/about`
15
- * - `false`: the default locale has no prefix — `/about`, `/pt/about`
16
- */
17
- prefixDefaultLocale?: boolean;
18
- validateExtraction?: boolean;
19
- translations?: Record<string, Record<string, any>>;
20
- kvBinding?: string;
21
- translationLoader?: string;
22
- }
23
- interface RimelightI18nVitePlugin {
24
- name: string;
25
- enforce?: "pre" | "post";
26
- resolveId?: (id: string) => string | null | undefined;
27
- load?: (id: string) => string | null | undefined;
28
- config?: (config: any) => any;
29
- closeBundle?: () => Promise<void> | void;
30
- [key: string]: any;
31
- }
32
- type RimelightI18nPlugins = RimelightI18nVitePlugin[];
33
- /**
34
- * Pure Vite plugin for Rimelight i18n. Provides virtual modules `@rimelight/i18n:runtime` and
35
- * `virtual:rimelight-i18n-config`.
36
- */
37
- declare function i18n(options?: RimelightI18nOptions): RimelightI18nPlugins;
38
- //#endregion
1
+ import { i as i18n, n as RimelightI18nPlugins, r as RimelightI18nVitePlugin, t as RimelightI18nOptions } from "./plugin-CFEm0e4U.mjs";
39
2
  export { RimelightI18nOptions, RimelightI18nPlugins, RimelightI18nVitePlugin, i18n, i18n as rimelightI18n };
package/dist/plugin.mjs CHANGED
@@ -1,55 +1,2 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- //#region src/plugin.ts
4
- /**
5
- * Pure Vite plugin for Rimelight i18n. Provides virtual modules `@rimelight/i18n:runtime` and
6
- * `virtual:rimelight-i18n-config`.
7
- */
8
- function i18n(options) {
9
- const translations = options?.translations;
10
- const kvBinding = options?.kvBinding ?? "TRANSLATIONS_KV";
11
- const locales = options?.locales ?? ["en"];
12
- const defaultLocale = options?.defaultLocale ?? "en";
13
- const prefixDefaultLocale = options?.prefixDefaultLocale ?? true;
14
- const normalisedTranslations = {};
15
- if (translations) for (const [locale, val] of Object.entries(translations)) normalisedTranslations[locale] = val;
16
- return [{
17
- name: "vite-plugin-rimelight-i18n-config",
18
- resolveId(id) {
19
- if (id === "virtual:rimelight-i18n-config") return "\0" + id;
20
- return null;
21
- },
22
- load(id) {
23
- if (id === "\0virtual:rimelight-i18n-config") return `
24
- export const locales = ${JSON.stringify(locales)};
25
- export const defaultLocale = ${JSON.stringify(defaultLocale)};
26
- export const prefixDefaultLocale = ${JSON.stringify(prefixDefaultLocale)};
27
- export const translations = ${JSON.stringify(normalisedTranslations)};
28
- `;
29
- return null;
30
- },
31
- async closeBundle() {
32
- if (!translations) return;
33
- const seedData = { keys: [] };
34
- for (const [locale, localeData] of Object.entries(translations)) for (const [component, componentData] of Object.entries(localeData)) seedData.keys.push({
35
- key: `locale:${locale}:${component}`,
36
- value: JSON.stringify(componentData)
37
- });
38
- const outDir = process.env.WRANGLER_OUT_DIR ?? "dist";
39
- const seedFile = path.join(outDir, "_translations-seed.json");
40
- try {
41
- fs.mkdirSync(path.dirname(seedFile), { recursive: true });
42
- fs.writeFileSync(seedFile, JSON.stringify(seedData, null, 2));
43
- if (process.env.WRANGLER_SEED_KV === "true" || process.env.CI === "true") {
44
- const { execSync } = await import("node:child_process");
45
- execSync(`npx wrangler kv:bulk put --binding "${kvBinding}" "${seedFile}"`, {
46
- stdio: "pipe",
47
- timeout: 3e4
48
- });
49
- }
50
- } catch {}
51
- }
52
- }];
53
- }
54
- //#endregion
1
+ import { t as i18n } from "./plugin-jh44sjC0.mjs";
55
2
  export { i18n, i18n as rimelightI18n };
@@ -0,0 +1,72 @@
1
+ import { Components, I18n, TranslationLoader, Translations, formatter } from "@nanostores/i18n";
2
+ //#region src/runtime.d.ts
3
+ /**
4
+ * A reactive store containing the current locale code. Set by middleware on each request, or
5
+ * manually via `currentLocale.set(locale)`.
6
+ */
7
+ declare const currentLocale: import("nanostores").PreinitializedWritableAtom<string> & object;
8
+ type I18nInstance = I18n;
9
+ type FormatterInstance = ReturnType<typeof formatter>;
10
+ interface InitializeI18nOptions {
11
+ /**
12
+ * The default locale code (e.g. 'en').
13
+ */
14
+ defaultLocale: string;
15
+ /**
16
+ * Pre-loaded translations keyed by locale then component.
17
+ */
18
+ translations: Record<string, Components>;
19
+ /**
20
+ * Optional dynamic loader called when a locale is not in cache.
21
+ */
22
+ get?: TranslationLoader;
23
+ }
24
+ /**
25
+ * Initializes the i18n system. Must be called once before any other i18n functions are used (the
26
+ * integration virtual module does this automatically).
27
+ */
28
+ declare function initializeI18n(options: InitializeI18nOptions): void;
29
+ /**
30
+ * Returns the underlying nanostores/i18n instance. Throws if not initialized.
31
+ */
32
+ declare function getI18nInstance(): I18nInstance;
33
+ /**
34
+ * Returns the formatter instance. Throws if not initialized.
35
+ */
36
+ declare function getFormatterInstance(): FormatterInstance;
37
+ /**
38
+ * Returns a Formatter object for the current locale with methods for formatting numbers, dates, and
39
+ * relative times using the native Intl API.
40
+ */
41
+ declare function useFormat(): ReturnType<FormatterInstance["get"]>;
42
+ /**
43
+ * Returns the translated strings for a component in the current locale. Falls back to defaultLocale
44
+ * loaded translations or `baseTranslations` if provided.
45
+ */
46
+ interface ComponentMessages {
47
+ [key: string]: string;
48
+ }
49
+ declare function useI18n(componentName: string): ComponentMessages;
50
+ declare function useI18n<Body extends Translations>(componentName: string, baseTranslations: Body): ComponentMessages & Body;
51
+ /**
52
+ * Async version of useI18n that waits for translations to finish loading.
53
+ */
54
+ declare function useI18nAsync(componentName: string): Promise<ComponentMessages>;
55
+ declare function useI18nAsync<Body extends Translations>(componentName: string, baseTranslations: Body): Promise<ComponentMessages & Body>;
56
+ /**
57
+ * Translates a single string key using dot-notation (e.g. `t("page_home.section_hero_title")`).
58
+ */
59
+ declare function t(key: string, params?: Record<string, any>): string;
60
+ /**
61
+ * Returns the plural category (e.g. 'one', 'few', 'many', 'other') for a given number and locale
62
+ * using the native browser Intl.PluralRules API.
63
+ */
64
+ declare function getPluralCategory(count: number, locale?: string): Intl.LDMLPluralRule;
65
+ /**
66
+ * Clears the translation cache.
67
+ *
68
+ * @param locale - If provided, clears only that locale's cache. Otherwise clears all.
69
+ */
70
+ declare function clearCache(locale?: string): void;
71
+ //#endregion
72
+ export { getFormatterInstance as a, initializeI18n as c, useI18n as d, useI18nAsync as f, currentLocale as i, t as l, InitializeI18nOptions as n, getI18nInstance as o, clearCache as r, getPluralCategory as s, ComponentMessages as t, useFormat as u };
@@ -0,0 +1,183 @@
1
+ import { createI18n, formatter, translationsLoading } from "@nanostores/i18n";
2
+ import { atom } from "nanostores";
3
+ //#region src/runtime.ts
4
+ /**
5
+ * A reactive store containing the current locale code. Set by middleware on each request, or
6
+ * manually via `currentLocale.set(locale)`.
7
+ */
8
+ const currentLocale = atom("");
9
+ let i18nInstance;
10
+ let formatterInstance;
11
+ let baseLocaleDefault = "en";
12
+ let rawTranslationsDict = {};
13
+ function throwNotInitialized() {
14
+ throw new Error("i18n not initialized. Call initializeI18n first.");
15
+ }
16
+ /**
17
+ * Wraps a TranslationLoader so that the returned object is guaranteed to contain a key for every
18
+ * requested component.
19
+ *
20
+ * The nanostores i18n library uses the keys of the object returned from `get` to clear its internal
21
+ * "requested" set. If a requested component is missing from the result (e.g. because the backend
22
+ * has no translations for it in the given locale yet), the internal loading atom is never set back
23
+ * to `false`, which causes useI18nAsync / translationsLoading to hang forever.
24
+ *
25
+ * This wrapper normalises the loader output so missing components are filled in with empty
26
+ * translation objects, falling back to the base translations defined at the call site.
27
+ */
28
+ function wrapLoader(loader) {
29
+ return async (code, components) => {
30
+ const raw = await loader(code, components);
31
+ const normalised = Array.isArray(raw) ? Object.assign({}, ...raw) : { ...raw };
32
+ for (const component of components) if (!(component in normalised)) normalised[component] = {};
33
+ return normalised;
34
+ };
35
+ }
36
+ /**
37
+ * Initializes the i18n system. Must be called once before any other i18n functions are used (the
38
+ * integration virtual module does this automatically).
39
+ */
40
+ function initializeI18n(options) {
41
+ const { defaultLocale, translations, get } = options;
42
+ baseLocaleDefault = defaultLocale;
43
+ rawTranslationsDict = translations || {};
44
+ if (!i18nInstance) {
45
+ currentLocale.set(defaultLocale);
46
+ const formattedCache = {};
47
+ for (const [locale, components] of Object.entries(translations)) {
48
+ formattedCache[locale] = {};
49
+ for (const [compName, compBody] of Object.entries(components)) {
50
+ let bodyObj = {};
51
+ if (isStringRecord(compBody)) bodyObj = compBody;
52
+ else if (isStoreWithGet(compBody)) {
53
+ const res = compBody.get();
54
+ if (isStringRecord(res)) bodyObj = res;
55
+ }
56
+ formattedCache[locale][compName] = atom(bodyObj);
57
+ }
58
+ }
59
+ i18nInstance = createI18n(currentLocale, {
60
+ baseLocale: defaultLocale,
61
+ get: wrapLoader(get ?? (async () => ({}))),
62
+ cache: formattedCache,
63
+ isSSR: true
64
+ });
65
+ }
66
+ formatterInstance = formatter(currentLocale);
67
+ }
68
+ /**
69
+ * Returns the underlying nanostores/i18n instance. Throws if not initialized.
70
+ */
71
+ function getI18nInstance() {
72
+ if (!i18nInstance) throwNotInitialized();
73
+ return i18nInstance;
74
+ }
75
+ /**
76
+ * Returns the formatter instance. Throws if not initialized.
77
+ */
78
+ function getFormatterInstance() {
79
+ if (!formatterInstance) throwNotInitialized();
80
+ return formatterInstance;
81
+ }
82
+ /**
83
+ * Returns a Formatter object for the current locale with methods for formatting numbers, dates, and
84
+ * relative times using the native Intl API.
85
+ */
86
+ function useFormat() {
87
+ return getFormatterInstance().get();
88
+ }
89
+ function isStoreWithGet(obj) {
90
+ if (typeof obj !== "object" || obj === null) return false;
91
+ return typeof Reflect.get(obj, "get") === "function";
92
+ }
93
+ function isStringRecord(obj) {
94
+ return typeof obj === "object" && obj !== null;
95
+ }
96
+ function getCachedComponent(locale, componentName) {
97
+ const normalizedLocale = locale.toLowerCase();
98
+ const candidates = [
99
+ locale,
100
+ normalizedLocale,
101
+ normalizedLocale.split("-")[0] ?? normalizedLocale,
102
+ baseLocaleDefault
103
+ ];
104
+ for (const code of candidates) {
105
+ const rawComp = rawTranslationsDict[code]?.[componentName];
106
+ if (isStoreWithGet(rawComp)) {
107
+ const res = rawComp.get();
108
+ if (isStringRecord(res)) return res;
109
+ } else if (isStringRecord(rawComp)) return rawComp;
110
+ }
111
+ if (!i18nInstance) return void 0;
112
+ for (const code of candidates) {
113
+ const rawComp = i18nInstance.cache[code]?.[componentName];
114
+ if (isStoreWithGet(rawComp)) {
115
+ const res = rawComp.get();
116
+ if (isStringRecord(res)) return res;
117
+ } else if (isStringRecord(rawComp)) return rawComp;
118
+ }
119
+ }
120
+ function useI18n(componentName, baseTranslations) {
121
+ const i18n = getI18nInstance();
122
+ const activeLocale = currentLocale.get() || baseLocaleDefault;
123
+ const fallbackDict = isStringRecord(baseTranslations) ? baseTranslations : {};
124
+ const baseDict = getCachedComponent(activeLocale, componentName) ?? getCachedComponent(baseLocaleDefault, componentName) ?? fallbackDict;
125
+ const targetObj = i18n(componentName, baseDict).get();
126
+ return new Proxy(targetObj, { get(target, prop) {
127
+ if (typeof prop === "symbol" || prop in Object.prototype) {
128
+ const val = Reflect.get(target, prop);
129
+ return typeof val === "string" ? val : "";
130
+ }
131
+ return target[prop] ?? baseDict[prop] ?? prop;
132
+ } });
133
+ }
134
+ async function useI18nAsync(componentName, baseTranslations) {
135
+ const i18n = getI18nInstance();
136
+ const cachedActive = getCachedComponent(currentLocale.get() || baseLocaleDefault, componentName);
137
+ const cachedDefault = getCachedComponent(baseLocaleDefault, componentName);
138
+ let baseDict = cachedActive ?? cachedDefault ?? {};
139
+ if (!cachedActive && !cachedDefault && isStringRecord(baseTranslations)) baseDict = baseTranslations;
140
+ const store = i18n(componentName, baseDict);
141
+ const unsubscribe = store.listen(() => {});
142
+ await translationsLoading(i18n);
143
+ unsubscribe();
144
+ return store.get();
145
+ }
146
+ /**
147
+ * Translates a single string key using dot-notation (e.g. `t("page_home.section_hero_title")`).
148
+ */
149
+ function t(key, params) {
150
+ const dotIndex = key.indexOf(".");
151
+ if (dotIndex === -1) return key;
152
+ const componentName = key.slice(0, dotIndex);
153
+ const keyName = key.slice(dotIndex + 1);
154
+ const activeLocale = currentLocale.get() || baseLocaleDefault;
155
+ let value = (getCachedComponent(activeLocale, componentName) ?? getCachedComponent(baseLocaleDefault, componentName) ?? {})[keyName] ?? key;
156
+ if (params && typeof params["count"] === "number" && typeof value === "object" && value !== null) {
157
+ const pluralCategory = getPluralCategory(params["count"], activeLocale);
158
+ value = value[pluralCategory] ?? value["other"] ?? key;
159
+ }
160
+ if (typeof value === "function") return value(params);
161
+ if (params && typeof value === "string") return value.replace(/\{(\w+)\}/g, (_, k) => String(params[k] ?? `{${k}}`));
162
+ return typeof value === "string" ? value : String(value);
163
+ }
164
+ /**
165
+ * Returns the plural category (e.g. 'one', 'few', 'many', 'other') for a given number and locale
166
+ * using the native browser Intl.PluralRules API.
167
+ */
168
+ function getPluralCategory(count, locale = "en") {
169
+ return new Intl.PluralRules(locale).select(count);
170
+ }
171
+ /**
172
+ * Clears the translation cache.
173
+ *
174
+ * @param locale - If provided, clears only that locale's cache. Otherwise clears all.
175
+ */
176
+ function clearCache(locale) {
177
+ if (!i18nInstance) throwNotInitialized();
178
+ const cache = i18nInstance.cache;
179
+ if (locale) cache[locale] = {};
180
+ else for (const key in cache) cache[key] = {};
181
+ }
182
+ //#endregion
183
+ export { getPluralCategory as a, useFormat as c, getI18nInstance as i, useI18n as l, currentLocale as n, initializeI18n as o, getFormatterInstance as r, t as s, clearCache as t, useI18nAsync as u };
@@ -1,72 +1,2 @@
1
- import { Components, I18n, TranslationLoader, Translations, formatter } from "@nanostores/i18n";
2
- //#region src/runtime.d.ts
3
- /**
4
- * A reactive store containing the current locale code. Set by middleware on each request, or
5
- * manually via `currentLocale.set(locale)`.
6
- */
7
- declare const currentLocale: import("nanostores").PreinitializedWritableAtom<string> & object;
8
- type I18nInstance = I18n;
9
- type FormatterInstance = ReturnType<typeof formatter>;
10
- interface InitializeI18nOptions {
11
- /**
12
- * The default locale code (e.g. 'en').
13
- */
14
- defaultLocale: string;
15
- /**
16
- * Pre-loaded translations keyed by locale then component.
17
- */
18
- translations: Record<string, Components>;
19
- /**
20
- * Optional dynamic loader called when a locale is not in cache.
21
- */
22
- get?: TranslationLoader;
23
- }
24
- /**
25
- * Initializes the i18n system. Must be called once before any other i18n functions are used (the
26
- * integration virtual module does this automatically).
27
- */
28
- declare function initializeI18n(options: InitializeI18nOptions): void;
29
- /**
30
- * Returns the underlying nanostores/i18n instance. Throws if not initialized.
31
- */
32
- declare function getI18nInstance(): I18nInstance;
33
- /**
34
- * Returns the formatter instance. Throws if not initialized.
35
- */
36
- declare function getFormatterInstance(): FormatterInstance;
37
- /**
38
- * Returns a Formatter object for the current locale with methods for formatting numbers, dates, and
39
- * relative times using the native Intl API.
40
- */
41
- declare function useFormat(): ReturnType<FormatterInstance["get"]>;
42
- /**
43
- * Returns the translated strings for a component in the current locale. Falls back to defaultLocale
44
- * loaded translations or `baseTranslations` if provided.
45
- */
46
- interface ComponentMessages {
47
- [key: string]: string;
48
- }
49
- declare function useI18n(componentName: string): ComponentMessages;
50
- declare function useI18n<Body extends Translations>(componentName: string, baseTranslations: Body): ComponentMessages & Body;
51
- /**
52
- * Async version of useI18n that waits for translations to finish loading.
53
- */
54
- declare function useI18nAsync(componentName: string): Promise<ComponentMessages>;
55
- declare function useI18nAsync<Body extends Translations>(componentName: string, baseTranslations: Body): Promise<ComponentMessages & Body>;
56
- /**
57
- * Translates a single string key using dot-notation (e.g. `t("page_home.section_hero_title")`).
58
- */
59
- declare function t(key: string, params?: Record<string, any>): string;
60
- /**
61
- * Returns the plural category (e.g. 'one', 'few', 'many', 'other') for a given number and locale
62
- * using the native browser Intl.PluralRules API.
63
- */
64
- declare function getPluralCategory(count: number, locale?: string): Intl.LDMLPluralRule;
65
- /**
66
- * Clears the translation cache.
67
- *
68
- * @param locale - If provided, clears only that locale's cache. Otherwise clears all.
69
- */
70
- declare function clearCache(locale?: string): void;
71
- //#endregion
1
+ import { a as getFormatterInstance, c as initializeI18n, d as useI18n, f as useI18nAsync, i as currentLocale, l as t, n as InitializeI18nOptions, o as getI18nInstance, r as clearCache, s as getPluralCategory, t as ComponentMessages, u as useFormat } from "./runtime-Bc8I8sA4.mjs";
72
2
  export { ComponentMessages, InitializeI18nOptions, clearCache, currentLocale, getFormatterInstance, getI18nInstance, getPluralCategory, initializeI18n, t, useFormat, useI18n, useI18nAsync };
package/dist/runtime.mjs CHANGED
@@ -1,183 +1,2 @@
1
- import { createI18n, formatter, translationsLoading } from "@nanostores/i18n";
2
- import { atom } from "nanostores";
3
- //#region src/runtime.ts
4
- /**
5
- * A reactive store containing the current locale code. Set by middleware on each request, or
6
- * manually via `currentLocale.set(locale)`.
7
- */
8
- const currentLocale = atom("");
9
- let i18nInstance;
10
- let formatterInstance;
11
- let baseLocaleDefault = "en";
12
- let rawTranslationsDict = {};
13
- function throwNotInitialized() {
14
- throw new Error("i18n not initialized. Call initializeI18n first.");
15
- }
16
- /**
17
- * Wraps a TranslationLoader so that the returned object is guaranteed to contain a key for every
18
- * requested component.
19
- *
20
- * The nanostores i18n library uses the keys of the object returned from `get` to clear its internal
21
- * "requested" set. If a requested component is missing from the result (e.g. because the backend
22
- * has no translations for it in the given locale yet), the internal loading atom is never set back
23
- * to `false`, which causes useI18nAsync / translationsLoading to hang forever.
24
- *
25
- * This wrapper normalises the loader output so missing components are filled in with empty
26
- * translation objects, falling back to the base translations defined at the call site.
27
- */
28
- function wrapLoader(loader) {
29
- return async (code, components) => {
30
- const raw = await loader(code, components);
31
- const normalised = Array.isArray(raw) ? Object.assign({}, ...raw) : { ...raw };
32
- for (const component of components) if (!(component in normalised)) normalised[component] = {};
33
- return normalised;
34
- };
35
- }
36
- /**
37
- * Initializes the i18n system. Must be called once before any other i18n functions are used (the
38
- * integration virtual module does this automatically).
39
- */
40
- function initializeI18n(options) {
41
- const { defaultLocale, translations, get } = options;
42
- baseLocaleDefault = defaultLocale;
43
- rawTranslationsDict = translations || {};
44
- if (!i18nInstance) {
45
- currentLocale.set(defaultLocale);
46
- const formattedCache = {};
47
- for (const [locale, components] of Object.entries(translations)) {
48
- formattedCache[locale] = {};
49
- for (const [compName, compBody] of Object.entries(components)) {
50
- let bodyObj = {};
51
- if (isStringRecord(compBody)) bodyObj = compBody;
52
- else if (isStoreWithGet(compBody)) {
53
- const res = compBody.get();
54
- if (isStringRecord(res)) bodyObj = res;
55
- }
56
- formattedCache[locale][compName] = atom(bodyObj);
57
- }
58
- }
59
- i18nInstance = createI18n(currentLocale, {
60
- baseLocale: defaultLocale,
61
- get: wrapLoader(get ?? (async () => ({}))),
62
- cache: formattedCache,
63
- isSSR: true
64
- });
65
- }
66
- formatterInstance = formatter(currentLocale);
67
- }
68
- /**
69
- * Returns the underlying nanostores/i18n instance. Throws if not initialized.
70
- */
71
- function getI18nInstance() {
72
- if (!i18nInstance) throwNotInitialized();
73
- return i18nInstance;
74
- }
75
- /**
76
- * Returns the formatter instance. Throws if not initialized.
77
- */
78
- function getFormatterInstance() {
79
- if (!formatterInstance) throwNotInitialized();
80
- return formatterInstance;
81
- }
82
- /**
83
- * Returns a Formatter object for the current locale with methods for formatting numbers, dates, and
84
- * relative times using the native Intl API.
85
- */
86
- function useFormat() {
87
- return getFormatterInstance().get();
88
- }
89
- function isStoreWithGet(obj) {
90
- if (typeof obj !== "object" || obj === null) return false;
91
- return typeof Reflect.get(obj, "get") === "function";
92
- }
93
- function isStringRecord(obj) {
94
- return typeof obj === "object" && obj !== null;
95
- }
96
- function getCachedComponent(locale, componentName) {
97
- const normalizedLocale = locale.toLowerCase();
98
- const candidates = [
99
- locale,
100
- normalizedLocale,
101
- normalizedLocale.split("-")[0] ?? normalizedLocale,
102
- baseLocaleDefault
103
- ];
104
- for (const code of candidates) {
105
- const rawComp = rawTranslationsDict[code]?.[componentName];
106
- if (isStoreWithGet(rawComp)) {
107
- const res = rawComp.get();
108
- if (isStringRecord(res)) return res;
109
- } else if (isStringRecord(rawComp)) return rawComp;
110
- }
111
- if (!i18nInstance) return void 0;
112
- for (const code of candidates) {
113
- const rawComp = i18nInstance.cache[code]?.[componentName];
114
- if (isStoreWithGet(rawComp)) {
115
- const res = rawComp.get();
116
- if (isStringRecord(res)) return res;
117
- } else if (isStringRecord(rawComp)) return rawComp;
118
- }
119
- }
120
- function useI18n(componentName, baseTranslations) {
121
- const i18n = getI18nInstance();
122
- const activeLocale = currentLocale.get() || baseLocaleDefault;
123
- const fallbackDict = isStringRecord(baseTranslations) ? baseTranslations : {};
124
- const baseDict = getCachedComponent(activeLocale, componentName) ?? getCachedComponent(baseLocaleDefault, componentName) ?? fallbackDict;
125
- const targetObj = i18n(componentName, baseDict).get();
126
- return new Proxy(targetObj, { get(target, prop) {
127
- if (typeof prop === "symbol" || prop in Object.prototype) {
128
- const val = Reflect.get(target, prop);
129
- return typeof val === "string" ? val : "";
130
- }
131
- return target[prop] ?? baseDict[prop] ?? prop;
132
- } });
133
- }
134
- async function useI18nAsync(componentName, baseTranslations) {
135
- const i18n = getI18nInstance();
136
- const cachedActive = getCachedComponent(currentLocale.get() || baseLocaleDefault, componentName);
137
- const cachedDefault = getCachedComponent(baseLocaleDefault, componentName);
138
- let baseDict = cachedActive ?? cachedDefault ?? {};
139
- if (!cachedActive && !cachedDefault && isStringRecord(baseTranslations)) baseDict = baseTranslations;
140
- const store = i18n(componentName, baseDict);
141
- const unsubscribe = store.listen(() => {});
142
- await translationsLoading(i18n);
143
- unsubscribe();
144
- return store.get();
145
- }
146
- /**
147
- * Translates a single string key using dot-notation (e.g. `t("page_home.section_hero_title")`).
148
- */
149
- function t(key, params) {
150
- const dotIndex = key.indexOf(".");
151
- if (dotIndex === -1) return key;
152
- const componentName = key.slice(0, dotIndex);
153
- const keyName = key.slice(dotIndex + 1);
154
- const activeLocale = currentLocale.get() || baseLocaleDefault;
155
- let value = (getCachedComponent(activeLocale, componentName) ?? getCachedComponent(baseLocaleDefault, componentName) ?? {})[keyName] ?? key;
156
- if (params && typeof params.count === "number" && typeof value === "object" && value !== null) {
157
- const pluralCategory = getPluralCategory(params.count, activeLocale);
158
- value = value[pluralCategory] ?? value.other ?? key;
159
- }
160
- if (typeof value === "function") return value(params);
161
- if (params && typeof value === "string") return value.replace(/\{(\w+)\}/g, (_, k) => String(params[k] ?? `{${k}}`));
162
- return typeof value === "string" ? value : String(value);
163
- }
164
- /**
165
- * Returns the plural category (e.g. 'one', 'few', 'many', 'other') for a given number and locale
166
- * using the native browser Intl.PluralRules API.
167
- */
168
- function getPluralCategory(count, locale = "en") {
169
- return new Intl.PluralRules(locale).select(count);
170
- }
171
- /**
172
- * Clears the translation cache.
173
- *
174
- * @param locale - If provided, clears only that locale's cache. Otherwise clears all.
175
- */
176
- function clearCache(locale) {
177
- if (!i18nInstance) throwNotInitialized();
178
- const cache = i18nInstance.cache;
179
- if (locale) cache[locale] = {};
180
- else for (const key in cache) cache[key] = {};
181
- }
182
- //#endregion
1
+ import { a as getPluralCategory, c as useFormat, i as getI18nInstance, l as useI18n, n as currentLocale, o as initializeI18n, r as getFormatterInstance, s as t, t as clearCache, u as useI18nAsync } from "./runtime-LitYNVlO.mjs";
183
2
  export { clearCache, currentLocale, getFormatterInstance, getI18nInstance, getPluralCategory, initializeI18n, t, useFormat, useI18n, useI18nAsync };
@@ -0,0 +1,121 @@
1
+ import { n as currentLocale, o as initializeI18n, s as t$1 } from "./runtime-LitYNVlO.mjs";
2
+ //#region src/index.ts
3
+ let locales = ["en"];
4
+ let defaultLocale = "en";
5
+ let prefixDefaultLocale = true;
6
+ let translations = {};
7
+ try {
8
+ const cfg = await import("virtual:rimelight-i18n-config");
9
+ if (cfg) {
10
+ if (cfg.locales) locales = cfg.locales;
11
+ if (cfg.defaultLocale) defaultLocale = cfg.defaultLocale;
12
+ if (cfg.prefixDefaultLocale !== void 0) prefixDefaultLocale = cfg.prefixDefaultLocale;
13
+ if (cfg.translations) translations = cfg.translations;
14
+ }
15
+ } catch {}
16
+ if (translations && Object.keys(translations).length > 0) initializeI18n({
17
+ defaultLocale: defaultLocale || "en",
18
+ translations
19
+ });
20
+ /**
21
+ * Translates a single string key using dot-notation (e.g. `t("page_home.section_hero_title")`).
22
+ * When augmented via CustomTranslations, keys are strictly type-checked and autocompleted.
23
+ */
24
+ function t(key, params) {
25
+ return t$1(key, params);
26
+ }
27
+ function getLocale() {
28
+ return currentLocale.get() || defaultLocale || "en";
29
+ }
30
+ /**
31
+ * Returns a standard BCP 47 / RFC 5646 language tag suitable for HTML `<html lang="...">`. Maps
32
+ * 2-letter codes like 'en' to 'en-US', 'pt' to 'pt-BR', 'es' to 'es-ES', etc.
33
+ */
34
+ function getHtmlLang() {
35
+ const loc = getLocale();
36
+ return {
37
+ en: "en-US",
38
+ pt: "pt-BR",
39
+ es: "es-ES"
40
+ }[loc] || loc;
41
+ }
42
+ /**
43
+ * Returns an OpenGraph-compliant locale tag (e.g. 'en_US', 'pt_BR', 'es_ES').
44
+ */
45
+ function getOgLocale() {
46
+ const loc = getLocale();
47
+ return {
48
+ en: "en_US",
49
+ pt: "pt_BR",
50
+ es: "es_ES"
51
+ }[loc] || `${loc}_${loc.toUpperCase()}`;
52
+ }
53
+ function getRelativeLocaleUrl(arg1, arg2) {
54
+ let targetLocale;
55
+ let targetPath;
56
+ if (arg2 !== void 0) {
57
+ targetLocale = arg1;
58
+ targetPath = arg2;
59
+ } else {
60
+ targetLocale = getLocale();
61
+ targetPath = arg1;
62
+ }
63
+ if (/^(?:[a-z]+:|\/\/|#)/i.test(targetPath) || targetPath.startsWith("mailto:") || targetPath.startsWith("tel:")) return targetPath;
64
+ const [pathWithoutQueryAndHash, queryAndHash] = (() => {
65
+ const match = targetPath.match(/^([^?#]*)([?#].*)?$/);
66
+ return [match?.[1] ?? "", match?.[2] ?? ""];
67
+ })();
68
+ const rawCleanPath = pathWithoutQueryAndHash.replace(/^\/+/, "");
69
+ const segments = rawCleanPath ? rawCleanPath.split("/") : [];
70
+ if (segments.length > 0 && locales.includes(segments[0])) segments.shift();
71
+ const cleanPath = segments.join("/");
72
+ const prefix = prefixDefaultLocale || targetLocale !== defaultLocale ? `/${targetLocale}` : "";
73
+ if (!cleanPath) return `${prefix ? `${prefix}/` : "/"}${queryAndHash}`;
74
+ return `${`${prefix}/${cleanPath}${pathWithoutQueryAndHash.endsWith("/") ? "/" : ""}`}${queryAndHash}`;
75
+ }
76
+ function getRelativeLocaleUrlList(path = "/") {
77
+ return locales.map((locale) => getRelativeLocaleUrl(locale, path));
78
+ }
79
+ /**
80
+ * Generates an array of hreflang alternates for SEO tags (<link rel="alternate" hreflang="...">).
81
+ * Automatically includes all supported locales and 'x-default'.
82
+ */
83
+ function getLanguageAlternates(pathname, siteUrl = "") {
84
+ const normalizedSiteUrl = siteUrl.replace(/\/+$/, "");
85
+ const alternates = locales.map((loc) => ({
86
+ hreflang: loc,
87
+ href: `${normalizedSiteUrl}${getRelativeLocaleUrl(loc, pathname)}`
88
+ }));
89
+ alternates.push({
90
+ hreflang: "x-default",
91
+ href: `${normalizedSiteUrl}${getRelativeLocaleUrl(defaultLocale, pathname)}`
92
+ });
93
+ return alternates;
94
+ }
95
+ let kvBinding = null;
96
+ function setKVBinding(kv) {
97
+ kvBinding = kv;
98
+ }
99
+ function createTranslationLoader(translations) {
100
+ return async (locale, components) => {
101
+ const localeTranslations = translations[locale] || {};
102
+ if (!components || !Array.isArray(components) || components.length === 0) return localeTranslations;
103
+ const activeKv = kvBinding;
104
+ if (activeKv) try {
105
+ const results = await Promise.all(components.map(async (name) => {
106
+ const key = `locale:${locale}:${name}`;
107
+ const data = await activeKv.get(key, "json");
108
+ if (data == null) return { [name]: localeTranslations[name] ?? {} };
109
+ return { [name]: data };
110
+ }));
111
+ return Object.assign({}, ...results);
112
+ } catch {}
113
+ const results = await Promise.all(components.map(async (name) => {
114
+ const data = localeTranslations[name];
115
+ return { [name]: data ?? {} };
116
+ }));
117
+ return Object.assign({}, ...results);
118
+ };
119
+ }
120
+ //#endregion
121
+ export { getLocale as a, getRelativeLocaleUrlList as c, setKVBinding as d, t as f, getLanguageAlternates as i, locales as l, defaultLocale as n, getOgLocale as o, getHtmlLang as r, getRelativeLocaleUrl as s, createTranslationLoader as t, prefixDefaultLocale as u };
@@ -0,0 +1,32 @@
1
+ import { ComponentsJSON } from "@nanostores/i18n";
2
+ //#region src/types.d.ts
3
+ interface FlattenedTranslations {
4
+ [key: string]: string;
5
+ }
6
+ interface LocaleFile {
7
+ [component: string]: {
8
+ [key: string]: string;
9
+ };
10
+ }
11
+ interface KVNamespaceBinding {
12
+ get(key: string, type: "json"): Promise<Record<string, string> | null>;
13
+ put(key: string, value: string): Promise<void>;
14
+ }
15
+ /**
16
+ * Derives dot-notated nested translation keys (e.g. "page_home.section_hero_title") from a
17
+ * translation schema type.
18
+ */
19
+ type NestedTranslationKeys<T> = T extends object ? { [K in keyof T & (string | number)]: T[K] extends object ? `${K}.${NestedTranslationKeys<T[K]>}` : `${K}`; }[keyof T & (string | number)] : never;
20
+ /**
21
+ * Augmented interface for app-level strongly typed translation keys. Applications can declare:
22
+ *
23
+ * ```ts
24
+ * declare module "@rimelight/i18n" {
25
+ * interface CustomTranslations extends typeof import("./translations/en.json") {}
26
+ * }
27
+ * ```
28
+ */
29
+ interface CustomTranslations {}
30
+ type TranslationKey = [keyof CustomTranslations] extends [never] ? string : NestedTranslationKeys<CustomTranslations>;
31
+ //#endregion
32
+ export { LocaleFile as a, KVNamespaceBinding as i, CustomTranslations as n, NestedTranslationKeys as o, FlattenedTranslations as r, TranslationKey as s, ComponentsJSON as t };
package/dist/types.d.mts CHANGED
@@ -1,32 +1,2 @@
1
- import { ComponentsJSON } from "@nanostores/i18n";
2
- //#region src/types.d.ts
3
- interface FlattenedTranslations {
4
- [key: string]: string;
5
- }
6
- interface LocaleFile {
7
- [component: string]: {
8
- [key: string]: string;
9
- };
10
- }
11
- interface KVNamespaceBinding {
12
- get(key: string, type: "json"): Promise<Record<string, string> | null>;
13
- put(key: string, value: string): Promise<void>;
14
- }
15
- /**
16
- * Derives dot-notated nested translation keys (e.g. "page_home.section_hero_title") from a
17
- * translation schema type.
18
- */
19
- type NestedTranslationKeys<T> = T extends object ? { [K in keyof T & (string | number)]: T[K] extends object ? `${K}.${NestedTranslationKeys<T[K]>}` : `${K}`; }[keyof T & (string | number)] : never;
20
- /**
21
- * Augmented interface for app-level strongly typed translation keys. Applications can declare:
22
- *
23
- * ```ts
24
- * declare module "@rimelight/i18n" {
25
- * interface CustomTranslations extends typeof import("./translations/en.json") {}
26
- * }
27
- * ```
28
- */
29
- interface CustomTranslations {}
30
- type TranslationKey = [keyof CustomTranslations] extends [never] ? string : NestedTranslationKeys<CustomTranslations>;
31
- //#endregion
1
+ import { a as LocaleFile, i as KVNamespaceBinding, n as CustomTranslations, o as NestedTranslationKeys, r as FlattenedTranslations, s as TranslationKey, t as ComponentsJSON } from "./types-BKUPwH-e.mjs";
32
2
  export { type ComponentsJSON, CustomTranslations, FlattenedTranslations, KVNamespaceBinding, LocaleFile, NestedTranslationKeys, TranslationKey };
package/dist/utils.d.mts CHANGED
@@ -1,12 +1,11 @@
1
- import { ComponentsJSON, FlattenedTranslations } from "./types.mjs";
1
+ import { r as FlattenedTranslations, t as ComponentsJSON } from "./types-BKUPwH-e.mjs";
2
2
  //#region src/utils.d.ts
3
- declare function flatten(obj: ComponentsJSON): FlattenedTranslations;
4
- declare function unflatten(flat: FlattenedTranslations): ComponentsJSON;
5
- declare function extractKeys(source: ComponentsJSON): string[];
3
+ export declare function flatten(obj: ComponentsJSON): FlattenedTranslations;
4
+ export declare function unflatten(flat: FlattenedTranslations): ComponentsJSON;
5
+ export declare function extractKeys(source: ComponentsJSON): string[];
6
6
  /**
7
7
  * Compares a target translation dictionary against a base/source translation dictionary, returning
8
8
  * any missing component keys.
9
9
  */
10
- declare function findMissingKeys(base: Record<string, Record<string, any>>, target: Record<string, Record<string, any>>): string[];
11
- //#endregion
12
- export { extractKeys, findMissingKeys, flatten, unflatten };
10
+ export declare function findMissingKeys(base: Record<string, Record<string, any>>, target: Record<string, Record<string, any>>): string[];
11
+ //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelight/i18n",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "private": false,
5
5
  "description": "Rimelight Entertainment's Internationalization Package",
6
6
  "homepage": "https://rimelight.com/docs",
@@ -53,11 +53,11 @@
53
53
  "nanostores": "1.5.3"
54
54
  },
55
55
  "devDependencies": {
56
- "@rimelight/config": "0.0.4",
56
+ "@rimelight/config": "0.0.6",
57
57
  "typescript": "6.0.3"
58
58
  },
59
59
  "engines": {
60
- "node": ">=26.7.0"
60
+ "node": ">=26.8.2"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "vp pack",