@better-intl/core 0.2.0 → 0.4.0

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.cjs CHANGED
@@ -45,21 +45,9 @@ function fnv1a(input) {
45
45
  }
46
46
  return hash;
47
47
  }
48
- function normalizePath(filePath) {
49
- let normalized = filePath.replace(/\\/g, "/");
50
- const markers = ["/src/", "/app/", "/pages/", "/components/", "/lib/"];
51
- for (const marker of markers) {
52
- const idx = normalized.indexOf(marker);
53
- if (idx !== -1) {
54
- normalized = normalized.slice(idx + 1);
55
- break;
56
- }
57
- }
58
- return normalized;
59
- }
60
48
  function generateId(input) {
61
- const filePath = normalizePath(input.filePath);
62
- const raw = `${filePath}::${input.context ?? ""}::${input.text}`;
49
+ const fileName = input.filePath.replace(/\\/g, "/").split("/").pop() ?? input.filePath;
50
+ const raw = `${fileName}::${input.context ?? ""}::${input.text}`;
63
51
  const hash = fnv1a(raw);
64
52
  return hash.toString(36).padStart(7, "0").slice(0, 7);
65
53
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/catalog.ts","../src/hash.ts","../src/interpolate.ts","../src/locale.ts","../src/runtime.ts","../src/types.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Message catalog — stores and resolves translations per locale.\n */\n\nexport interface MessageDescriptor {\n id: string;\n defaultMessage: string;\n description?: string;\n}\n\nexport type Messages = Record<string, string>;\nexport type CatalogData = Record<string, Messages>;\n\nexport class Catalog {\n private data: CatalogData = {};\n private defaultLocale: string;\n\n constructor(defaultLocale: string = \"en\") {\n this.defaultLocale = defaultLocale;\n }\n\n load(locale: string, messages: Messages): void {\n this.data[locale] = { ...this.data[locale], ...messages };\n }\n\n resolve(id: string, locale: string, defaultMessage?: string): string {\n return (\n this.data[locale]?.[id] ??\n this.data[this.defaultLocale]?.[id] ??\n defaultMessage ??\n id\n );\n }\n\n has(id: string, locale: string): boolean {\n return id in (this.data[locale] ?? {});\n }\n\n getLocales(): string[] {\n return Object.keys(this.data);\n }\n\n getMessages(locale: string): Messages | undefined {\n return this.data[locale];\n }\n\n getAllIds(): Set<string> {\n const ids = new Set<string>();\n for (const messages of Object.values(this.data)) {\n for (const id of Object.keys(messages)) {\n ids.add(id);\n }\n }\n return ids;\n }\n\n toJSON(): CatalogData {\n return structuredClone(this.data);\n }\n}\n","/**\n * Stable hash generation for message IDs.\n *\n * Produces a short, deterministic hash from the message text,\n * file path, and component context. Uses FNV-1a for speed and\n * low collision rate at short lengths.\n */\n\nfunction fnv1a(input: string): number {\n let hash = 0x811c9dc5; // FNV offset basis\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n hash = (hash * 0x01000193) >>> 0; // FNV prime, keep as uint32\n }\n return hash;\n}\n\nexport interface HashInput {\n text: string;\n filePath: string;\n context?: string;\n}\n\n/**\n * Normalize file path for consistent hashing across environments.\n * Strips absolute prefixes and normalizes separators.\n */\nfunction normalizePath(filePath: string): string {\n // Normalize backslashes to forward slashes\n let normalized = filePath.replace(/\\\\/g, \"/\");\n // Strip common absolute prefixes to get a project-relative path\n // e.g. /Users/.../project/src/Hero.tsx → src/Hero.tsx\n const markers = [\"/src/\", \"/app/\", \"/pages/\", \"/components/\", \"/lib/\"];\n for (const marker of markers) {\n const idx = normalized.indexOf(marker);\n if (idx !== -1) {\n normalized = normalized.slice(idx + 1);\n break;\n }\n }\n return normalized;\n}\n\n/**\n * Generate a stable, short message ID.\n *\n * @example\n * generateId({ text: \"Hello world\", filePath: \"src/Hero.tsx\", context: \"h1\" })\n * // => \"a1b2c3d4\"\n */\nexport function generateId(input: HashInput): string {\n const filePath = normalizePath(input.filePath);\n const raw = `${filePath}::${input.context ?? \"\"}::${input.text}`;\n const hash = fnv1a(raw);\n return hash.toString(36).padStart(7, \"0\").slice(0, 7);\n}\n","/**\n * ICU-lite interpolation and pluralization.\n *\n * Supports:\n * - Simple variables: \"Hello {name}\"\n * - Plural: \"{count, plural, one {# item} other {# items}}\"\n * - Select: \"{gender, select, male {He} female {She} other {They}}\"\n */\n\nexport type InterpolationValues = Record<string, string | number>;\n\nconst VARIABLE_RE = /\\{(\\w+)\\}/g;\nconst PLURAL_RE = /\\{(\\w+),\\s*plural,\\s*((?:\\w+\\s*\\{[^}]*\\}\\s*)+)\\}/g;\nconst SELECT_RE = /\\{(\\w+),\\s*select,\\s*((?:\\w+\\s*\\{[^}]*\\}\\s*)+)\\}/g;\nconst BRANCH_RE = /(\\w+)\\s*\\{([^}]*)\\}/g;\n\nfunction parseBranches(raw: string): Record<string, string> {\n const branches: Record<string, string> = {};\n let match: RegExpExecArray | null;\n BRANCH_RE.lastIndex = 0;\n // biome-ignore lint/suspicious/noAssignInExpressions: regex exec loop pattern\n while ((match = BRANCH_RE.exec(raw))) {\n branches[match[1]] = match[2];\n }\n return branches;\n}\n\nfunction resolvePlural(\n count: number,\n branches: Record<string, string>,\n): string {\n const key =\n count === 0 ? \"zero\" : count === 1 ? \"one\" : count === 2 ? \"two\" : \"other\";\n\n const template = branches[key] ?? branches.other ?? \"\";\n return template.replace(/#/g, String(count));\n}\n\nexport function interpolate(\n message: string,\n values: InterpolationValues = {},\n): string {\n let result = message;\n\n // Plural\n result = result.replace(PLURAL_RE, (_, name, raw) => {\n const count = Number(values[name] ?? 0);\n return resolvePlural(count, parseBranches(raw));\n });\n\n // Select\n result = result.replace(SELECT_RE, (_, name, raw) => {\n const value = String(values[name] ?? \"\");\n const branches = parseBranches(raw);\n return branches[value] ?? branches.other ?? \"\";\n });\n\n // Simple variables\n result = result.replace(VARIABLE_RE, (_, name) =>\n values[name] != null ? String(values[name]) : `{${name}}`,\n );\n\n return result;\n}\n","/**\n * Locale resolution and negotiation.\n */\n\nexport interface LocaleConfig {\n defaultLocale: string;\n locales: string[];\n fallback?: Record<string, string>;\n}\n\n/**\n * Negotiate the best locale from a list of preferred locales\n * (e.g. from Accept-Language header) against supported locales.\n */\nexport function negotiateLocale(\n preferred: readonly string[],\n supported: readonly string[],\n defaultLocale: string,\n): string {\n const supportedSet = new Set(supported.map((l) => l.toLowerCase()));\n\n for (const pref of preferred) {\n const lower = pref.toLowerCase();\n\n // Exact match\n if (supportedSet.has(lower)) {\n return supported.find((s) => s.toLowerCase() === lower) as string;\n }\n\n // Language-only match (e.g. \"en-US\" -> \"en\")\n const lang = lower.split(\"-\")[0];\n if (supportedSet.has(lang)) {\n return supported.find((s) => s.toLowerCase() === lang) as string;\n }\n\n // Supported locale that starts with the language\n const partial = supported.find((s) =>\n s.toLowerCase().startsWith(`${lang}-`),\n );\n if (partial) return partial;\n }\n\n return defaultLocale;\n}\n\n/**\n * Parse Accept-Language header into ordered locale list.\n */\nexport function parseAcceptLanguage(header: string): string[] {\n return header\n .split(\",\")\n .map((part) => {\n const [locale, q] = part.trim().split(\";q=\");\n return { locale: locale.trim(), quality: q ? parseFloat(q) : 1 };\n })\n .sort((a, b) => b.quality - a.quality)\n .map((entry) => entry.locale);\n}\n","/**\n * Minimal runtime — the `t()` function used at runtime after compilation.\n *\n * The compiler transforms `<h1>Hello world</h1>` into `<h1>{t(\"a1b2c3\")}</h1>`.\n * This module provides the `t` function that resolves the message at runtime.\n */\n\nimport { Catalog, type Messages } from \"./catalog.js\";\nimport { type InterpolationValues, interpolate } from \"./interpolate.js\";\n\nexport interface IntlRuntime {\n locale: string;\n t(id: string, values?: InterpolationValues, defaultMessage?: string): string;\n loadMessages(locale: string, messages: Messages): void;\n setLocale(locale: string): void;\n}\n\nexport function createIntlRuntime(\n defaultLocale: string,\n initialMessages?: Messages,\n): IntlRuntime {\n const catalog = new Catalog(defaultLocale);\n let currentLocale = defaultLocale;\n\n if (initialMessages) {\n catalog.load(defaultLocale, initialMessages);\n }\n\n return {\n get locale() {\n return currentLocale;\n },\n\n t(\n id: string,\n values?: InterpolationValues,\n defaultMessage?: string,\n ): string {\n const raw = catalog.resolve(id, currentLocale, defaultMessage);\n return values ? interpolate(raw, values) : raw;\n },\n\n loadMessages(locale: string, messages: Messages) {\n catalog.load(locale, messages);\n },\n\n setLocale(locale: string) {\n currentLocale = locale;\n },\n };\n}\n","/**\n * Shared types for the better-intl ecosystem.\n */\n\nexport interface ExtractedMessage {\n id: string;\n defaultMessage: string;\n description: string;\n filePath: string;\n componentName?: string;\n elementType?: string;\n line: number;\n column: number;\n}\n\nexport interface BetterIntlConfig {\n defaultLocale: string;\n locales: string[];\n catalogs: string;\n include: string[];\n exclude: string[];\n fallback?: Record<string, string>;\n}\n\nexport const DEFAULT_CONFIG: BetterIntlConfig = {\n defaultLocale: \"en\",\n locales: [\"en\"],\n catalogs: \"./locales/{locale}.json\",\n include: [\"src/**/*.tsx\", \"src/**/*.jsx\", \"app/**/*.tsx\", \"app/**/*.jsx\"],\n exclude: [\n \"**/*.test.*\",\n \"**/*.spec.*\",\n \"**/*.stories.*\",\n \"**/node_modules/**\",\n ],\n};\n"]}
1
+ {"version":3,"sources":["../src/catalog.ts","../src/hash.ts","../src/interpolate.ts","../src/locale.ts","../src/runtime.ts","../src/types.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Message catalog — stores and resolves translations per locale.\n */\n\nexport interface MessageDescriptor {\n id: string;\n defaultMessage: string;\n description?: string;\n}\n\nexport type Messages = Record<string, string>;\nexport type CatalogData = Record<string, Messages>;\n\nexport class Catalog {\n private data: CatalogData = {};\n private defaultLocale: string;\n\n constructor(defaultLocale: string = \"en\") {\n this.defaultLocale = defaultLocale;\n }\n\n load(locale: string, messages: Messages): void {\n this.data[locale] = { ...this.data[locale], ...messages };\n }\n\n resolve(id: string, locale: string, defaultMessage?: string): string {\n return (\n this.data[locale]?.[id] ??\n this.data[this.defaultLocale]?.[id] ??\n defaultMessage ??\n id\n );\n }\n\n has(id: string, locale: string): boolean {\n return id in (this.data[locale] ?? {});\n }\n\n getLocales(): string[] {\n return Object.keys(this.data);\n }\n\n getMessages(locale: string): Messages | undefined {\n return this.data[locale];\n }\n\n getAllIds(): Set<string> {\n const ids = new Set<string>();\n for (const messages of Object.values(this.data)) {\n for (const id of Object.keys(messages)) {\n ids.add(id);\n }\n }\n return ids;\n }\n\n toJSON(): CatalogData {\n return structuredClone(this.data);\n }\n}\n","/**\n * Stable hash generation for message IDs.\n *\n * Produces a short, deterministic hash from the message text,\n * file path, and component context. Uses FNV-1a for speed and\n * low collision rate at short lengths.\n */\n\nfunction fnv1a(input: string): number {\n let hash = 0x811c9dc5; // FNV offset basis\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n hash = (hash * 0x01000193) >>> 0; // FNV prime, keep as uint32\n }\n return hash;\n}\n\nexport interface HashInput {\n text: string;\n filePath: string;\n context?: string;\n}\n\n/**\n * Generate a stable, short message ID.\n *\n * Uses only the file name (not directory path) to avoid hash\n * mismatches between different tools (CLI extract vs build loader)\n * that may resolve paths differently.\n *\n * @example\n * generateId({ text: \"Hello world\", filePath: \"src/Hero.tsx\", context: \"Hero\" })\n * // => \"a1b2c3d\"\n */\nexport function generateId(input: HashInput): string {\n // Use only the file name — immune to path resolution differences\n const fileName =\n input.filePath.replace(/\\\\/g, \"/\").split(\"/\").pop() ?? input.filePath;\n const raw = `${fileName}::${input.context ?? \"\"}::${input.text}`;\n const hash = fnv1a(raw);\n return hash.toString(36).padStart(7, \"0\").slice(0, 7);\n}\n","/**\n * ICU-lite interpolation and pluralization.\n *\n * Supports:\n * - Simple variables: \"Hello {name}\"\n * - Plural: \"{count, plural, one {# item} other {# items}}\"\n * - Select: \"{gender, select, male {He} female {She} other {They}}\"\n */\n\nexport type InterpolationValues = Record<string, string | number>;\n\nconst VARIABLE_RE = /\\{(\\w+)\\}/g;\nconst PLURAL_RE = /\\{(\\w+),\\s*plural,\\s*((?:\\w+\\s*\\{[^}]*\\}\\s*)+)\\}/g;\nconst SELECT_RE = /\\{(\\w+),\\s*select,\\s*((?:\\w+\\s*\\{[^}]*\\}\\s*)+)\\}/g;\nconst BRANCH_RE = /(\\w+)\\s*\\{([^}]*)\\}/g;\n\nfunction parseBranches(raw: string): Record<string, string> {\n const branches: Record<string, string> = {};\n let match: RegExpExecArray | null;\n BRANCH_RE.lastIndex = 0;\n // biome-ignore lint/suspicious/noAssignInExpressions: regex exec loop pattern\n while ((match = BRANCH_RE.exec(raw))) {\n branches[match[1]] = match[2];\n }\n return branches;\n}\n\nfunction resolvePlural(\n count: number,\n branches: Record<string, string>,\n): string {\n const key =\n count === 0 ? \"zero\" : count === 1 ? \"one\" : count === 2 ? \"two\" : \"other\";\n\n const template = branches[key] ?? branches.other ?? \"\";\n return template.replace(/#/g, String(count));\n}\n\nexport function interpolate(\n message: string,\n values: InterpolationValues = {},\n): string {\n let result = message;\n\n // Plural\n result = result.replace(PLURAL_RE, (_, name, raw) => {\n const count = Number(values[name] ?? 0);\n return resolvePlural(count, parseBranches(raw));\n });\n\n // Select\n result = result.replace(SELECT_RE, (_, name, raw) => {\n const value = String(values[name] ?? \"\");\n const branches = parseBranches(raw);\n return branches[value] ?? branches.other ?? \"\";\n });\n\n // Simple variables\n result = result.replace(VARIABLE_RE, (_, name) =>\n values[name] != null ? String(values[name]) : `{${name}}`,\n );\n\n return result;\n}\n","/**\n * Locale resolution and negotiation.\n */\n\nexport interface LocaleConfig {\n defaultLocale: string;\n locales: string[];\n fallback?: Record<string, string>;\n}\n\n/**\n * Negotiate the best locale from a list of preferred locales\n * (e.g. from Accept-Language header) against supported locales.\n */\nexport function negotiateLocale(\n preferred: readonly string[],\n supported: readonly string[],\n defaultLocale: string,\n): string {\n const supportedSet = new Set(supported.map((l) => l.toLowerCase()));\n\n for (const pref of preferred) {\n const lower = pref.toLowerCase();\n\n // Exact match\n if (supportedSet.has(lower)) {\n return supported.find((s) => s.toLowerCase() === lower) as string;\n }\n\n // Language-only match (e.g. \"en-US\" -> \"en\")\n const lang = lower.split(\"-\")[0];\n if (supportedSet.has(lang)) {\n return supported.find((s) => s.toLowerCase() === lang) as string;\n }\n\n // Supported locale that starts with the language\n const partial = supported.find((s) =>\n s.toLowerCase().startsWith(`${lang}-`),\n );\n if (partial) return partial;\n }\n\n return defaultLocale;\n}\n\n/**\n * Parse Accept-Language header into ordered locale list.\n */\nexport function parseAcceptLanguage(header: string): string[] {\n return header\n .split(\",\")\n .map((part) => {\n const [locale, q] = part.trim().split(\";q=\");\n return { locale: locale.trim(), quality: q ? parseFloat(q) : 1 };\n })\n .sort((a, b) => b.quality - a.quality)\n .map((entry) => entry.locale);\n}\n","/**\n * Minimal runtime — the `t()` function used at runtime after compilation.\n *\n * The compiler transforms `<h1>Hello world</h1>` into `<h1>{t(\"a1b2c3\")}</h1>`.\n * This module provides the `t` function that resolves the message at runtime.\n */\n\nimport { Catalog, type Messages } from \"./catalog.js\";\nimport { type InterpolationValues, interpolate } from \"./interpolate.js\";\n\nexport interface IntlRuntime {\n locale: string;\n t(id: string, values?: InterpolationValues, defaultMessage?: string): string;\n loadMessages(locale: string, messages: Messages): void;\n setLocale(locale: string): void;\n}\n\nexport function createIntlRuntime(\n defaultLocale: string,\n initialMessages?: Messages,\n): IntlRuntime {\n const catalog = new Catalog(defaultLocale);\n let currentLocale = defaultLocale;\n\n if (initialMessages) {\n catalog.load(defaultLocale, initialMessages);\n }\n\n return {\n get locale() {\n return currentLocale;\n },\n\n t(\n id: string,\n values?: InterpolationValues,\n defaultMessage?: string,\n ): string {\n const raw = catalog.resolve(id, currentLocale, defaultMessage);\n return values ? interpolate(raw, values) : raw;\n },\n\n loadMessages(locale: string, messages: Messages) {\n catalog.load(locale, messages);\n },\n\n setLocale(locale: string) {\n currentLocale = locale;\n },\n };\n}\n","/**\n * Shared types for the better-intl ecosystem.\n */\n\nexport interface ExtractedMessage {\n id: string;\n defaultMessage: string;\n description: string;\n filePath: string;\n componentName?: string;\n elementType?: string;\n line: number;\n column: number;\n}\n\nexport interface BetterIntlConfig {\n defaultLocale: string;\n locales: string[];\n catalogs: string;\n include: string[];\n exclude: string[];\n fallback?: Record<string, string>;\n}\n\nexport const DEFAULT_CONFIG: BetterIntlConfig = {\n defaultLocale: \"en\",\n locales: [\"en\"],\n catalogs: \"./locales/{locale}.json\",\n include: [\"src/**/*.tsx\", \"src/**/*.jsx\", \"app/**/*.tsx\", \"app/**/*.jsx\"],\n exclude: [\n \"**/*.test.*\",\n \"**/*.spec.*\",\n \"**/*.stories.*\",\n \"**/node_modules/**\",\n ],\n};\n"]}
package/dist/index.d.cts CHANGED
@@ -36,9 +36,13 @@ interface HashInput {
36
36
  /**
37
37
  * Generate a stable, short message ID.
38
38
  *
39
+ * Uses only the file name (not directory path) to avoid hash
40
+ * mismatches between different tools (CLI extract vs build loader)
41
+ * that may resolve paths differently.
42
+ *
39
43
  * @example
40
- * generateId({ text: "Hello world", filePath: "src/Hero.tsx", context: "h1" })
41
- * // => "a1b2c3d4"
44
+ * generateId({ text: "Hello world", filePath: "src/Hero.tsx", context: "Hero" })
45
+ * // => "a1b2c3d"
42
46
  */
43
47
  declare function generateId(input: HashInput): string;
44
48
 
package/dist/index.d.ts CHANGED
@@ -36,9 +36,13 @@ interface HashInput {
36
36
  /**
37
37
  * Generate a stable, short message ID.
38
38
  *
39
+ * Uses only the file name (not directory path) to avoid hash
40
+ * mismatches between different tools (CLI extract vs build loader)
41
+ * that may resolve paths differently.
42
+ *
39
43
  * @example
40
- * generateId({ text: "Hello world", filePath: "src/Hero.tsx", context: "h1" })
41
- * // => "a1b2c3d4"
44
+ * generateId({ text: "Hello world", filePath: "src/Hero.tsx", context: "Hero" })
45
+ * // => "a1b2c3d"
42
46
  */
43
47
  declare function generateId(input: HashInput): string;
44
48
 
package/dist/index.js CHANGED
@@ -43,21 +43,9 @@ function fnv1a(input) {
43
43
  }
44
44
  return hash;
45
45
  }
46
- function normalizePath(filePath) {
47
- let normalized = filePath.replace(/\\/g, "/");
48
- const markers = ["/src/", "/app/", "/pages/", "/components/", "/lib/"];
49
- for (const marker of markers) {
50
- const idx = normalized.indexOf(marker);
51
- if (idx !== -1) {
52
- normalized = normalized.slice(idx + 1);
53
- break;
54
- }
55
- }
56
- return normalized;
57
- }
58
46
  function generateId(input) {
59
- const filePath = normalizePath(input.filePath);
60
- const raw = `${filePath}::${input.context ?? ""}::${input.text}`;
47
+ const fileName = input.filePath.replace(/\\/g, "/").split("/").pop() ?? input.filePath;
48
+ const raw = `${fileName}::${input.context ?? ""}::${input.text}`;
61
49
  const hash = fnv1a(raw);
62
50
  return hash.toString(36).padStart(7, "0").slice(0, 7);
63
51
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/catalog.ts","../src/hash.ts","../src/interpolate.ts","../src/locale.ts","../src/runtime.ts","../src/types.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Message catalog — stores and resolves translations per locale.\n */\n\nexport interface MessageDescriptor {\n id: string;\n defaultMessage: string;\n description?: string;\n}\n\nexport type Messages = Record<string, string>;\nexport type CatalogData = Record<string, Messages>;\n\nexport class Catalog {\n private data: CatalogData = {};\n private defaultLocale: string;\n\n constructor(defaultLocale: string = \"en\") {\n this.defaultLocale = defaultLocale;\n }\n\n load(locale: string, messages: Messages): void {\n this.data[locale] = { ...this.data[locale], ...messages };\n }\n\n resolve(id: string, locale: string, defaultMessage?: string): string {\n return (\n this.data[locale]?.[id] ??\n this.data[this.defaultLocale]?.[id] ??\n defaultMessage ??\n id\n );\n }\n\n has(id: string, locale: string): boolean {\n return id in (this.data[locale] ?? {});\n }\n\n getLocales(): string[] {\n return Object.keys(this.data);\n }\n\n getMessages(locale: string): Messages | undefined {\n return this.data[locale];\n }\n\n getAllIds(): Set<string> {\n const ids = new Set<string>();\n for (const messages of Object.values(this.data)) {\n for (const id of Object.keys(messages)) {\n ids.add(id);\n }\n }\n return ids;\n }\n\n toJSON(): CatalogData {\n return structuredClone(this.data);\n }\n}\n","/**\n * Stable hash generation for message IDs.\n *\n * Produces a short, deterministic hash from the message text,\n * file path, and component context. Uses FNV-1a for speed and\n * low collision rate at short lengths.\n */\n\nfunction fnv1a(input: string): number {\n let hash = 0x811c9dc5; // FNV offset basis\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n hash = (hash * 0x01000193) >>> 0; // FNV prime, keep as uint32\n }\n return hash;\n}\n\nexport interface HashInput {\n text: string;\n filePath: string;\n context?: string;\n}\n\n/**\n * Normalize file path for consistent hashing across environments.\n * Strips absolute prefixes and normalizes separators.\n */\nfunction normalizePath(filePath: string): string {\n // Normalize backslashes to forward slashes\n let normalized = filePath.replace(/\\\\/g, \"/\");\n // Strip common absolute prefixes to get a project-relative path\n // e.g. /Users/.../project/src/Hero.tsx → src/Hero.tsx\n const markers = [\"/src/\", \"/app/\", \"/pages/\", \"/components/\", \"/lib/\"];\n for (const marker of markers) {\n const idx = normalized.indexOf(marker);\n if (idx !== -1) {\n normalized = normalized.slice(idx + 1);\n break;\n }\n }\n return normalized;\n}\n\n/**\n * Generate a stable, short message ID.\n *\n * @example\n * generateId({ text: \"Hello world\", filePath: \"src/Hero.tsx\", context: \"h1\" })\n * // => \"a1b2c3d4\"\n */\nexport function generateId(input: HashInput): string {\n const filePath = normalizePath(input.filePath);\n const raw = `${filePath}::${input.context ?? \"\"}::${input.text}`;\n const hash = fnv1a(raw);\n return hash.toString(36).padStart(7, \"0\").slice(0, 7);\n}\n","/**\n * ICU-lite interpolation and pluralization.\n *\n * Supports:\n * - Simple variables: \"Hello {name}\"\n * - Plural: \"{count, plural, one {# item} other {# items}}\"\n * - Select: \"{gender, select, male {He} female {She} other {They}}\"\n */\n\nexport type InterpolationValues = Record<string, string | number>;\n\nconst VARIABLE_RE = /\\{(\\w+)\\}/g;\nconst PLURAL_RE = /\\{(\\w+),\\s*plural,\\s*((?:\\w+\\s*\\{[^}]*\\}\\s*)+)\\}/g;\nconst SELECT_RE = /\\{(\\w+),\\s*select,\\s*((?:\\w+\\s*\\{[^}]*\\}\\s*)+)\\}/g;\nconst BRANCH_RE = /(\\w+)\\s*\\{([^}]*)\\}/g;\n\nfunction parseBranches(raw: string): Record<string, string> {\n const branches: Record<string, string> = {};\n let match: RegExpExecArray | null;\n BRANCH_RE.lastIndex = 0;\n // biome-ignore lint/suspicious/noAssignInExpressions: regex exec loop pattern\n while ((match = BRANCH_RE.exec(raw))) {\n branches[match[1]] = match[2];\n }\n return branches;\n}\n\nfunction resolvePlural(\n count: number,\n branches: Record<string, string>,\n): string {\n const key =\n count === 0 ? \"zero\" : count === 1 ? \"one\" : count === 2 ? \"two\" : \"other\";\n\n const template = branches[key] ?? branches.other ?? \"\";\n return template.replace(/#/g, String(count));\n}\n\nexport function interpolate(\n message: string,\n values: InterpolationValues = {},\n): string {\n let result = message;\n\n // Plural\n result = result.replace(PLURAL_RE, (_, name, raw) => {\n const count = Number(values[name] ?? 0);\n return resolvePlural(count, parseBranches(raw));\n });\n\n // Select\n result = result.replace(SELECT_RE, (_, name, raw) => {\n const value = String(values[name] ?? \"\");\n const branches = parseBranches(raw);\n return branches[value] ?? branches.other ?? \"\";\n });\n\n // Simple variables\n result = result.replace(VARIABLE_RE, (_, name) =>\n values[name] != null ? String(values[name]) : `{${name}}`,\n );\n\n return result;\n}\n","/**\n * Locale resolution and negotiation.\n */\n\nexport interface LocaleConfig {\n defaultLocale: string;\n locales: string[];\n fallback?: Record<string, string>;\n}\n\n/**\n * Negotiate the best locale from a list of preferred locales\n * (e.g. from Accept-Language header) against supported locales.\n */\nexport function negotiateLocale(\n preferred: readonly string[],\n supported: readonly string[],\n defaultLocale: string,\n): string {\n const supportedSet = new Set(supported.map((l) => l.toLowerCase()));\n\n for (const pref of preferred) {\n const lower = pref.toLowerCase();\n\n // Exact match\n if (supportedSet.has(lower)) {\n return supported.find((s) => s.toLowerCase() === lower) as string;\n }\n\n // Language-only match (e.g. \"en-US\" -> \"en\")\n const lang = lower.split(\"-\")[0];\n if (supportedSet.has(lang)) {\n return supported.find((s) => s.toLowerCase() === lang) as string;\n }\n\n // Supported locale that starts with the language\n const partial = supported.find((s) =>\n s.toLowerCase().startsWith(`${lang}-`),\n );\n if (partial) return partial;\n }\n\n return defaultLocale;\n}\n\n/**\n * Parse Accept-Language header into ordered locale list.\n */\nexport function parseAcceptLanguage(header: string): string[] {\n return header\n .split(\",\")\n .map((part) => {\n const [locale, q] = part.trim().split(\";q=\");\n return { locale: locale.trim(), quality: q ? parseFloat(q) : 1 };\n })\n .sort((a, b) => b.quality - a.quality)\n .map((entry) => entry.locale);\n}\n","/**\n * Minimal runtime — the `t()` function used at runtime after compilation.\n *\n * The compiler transforms `<h1>Hello world</h1>` into `<h1>{t(\"a1b2c3\")}</h1>`.\n * This module provides the `t` function that resolves the message at runtime.\n */\n\nimport { Catalog, type Messages } from \"./catalog.js\";\nimport { type InterpolationValues, interpolate } from \"./interpolate.js\";\n\nexport interface IntlRuntime {\n locale: string;\n t(id: string, values?: InterpolationValues, defaultMessage?: string): string;\n loadMessages(locale: string, messages: Messages): void;\n setLocale(locale: string): void;\n}\n\nexport function createIntlRuntime(\n defaultLocale: string,\n initialMessages?: Messages,\n): IntlRuntime {\n const catalog = new Catalog(defaultLocale);\n let currentLocale = defaultLocale;\n\n if (initialMessages) {\n catalog.load(defaultLocale, initialMessages);\n }\n\n return {\n get locale() {\n return currentLocale;\n },\n\n t(\n id: string,\n values?: InterpolationValues,\n defaultMessage?: string,\n ): string {\n const raw = catalog.resolve(id, currentLocale, defaultMessage);\n return values ? interpolate(raw, values) : raw;\n },\n\n loadMessages(locale: string, messages: Messages) {\n catalog.load(locale, messages);\n },\n\n setLocale(locale: string) {\n currentLocale = locale;\n },\n };\n}\n","/**\n * Shared types for the better-intl ecosystem.\n */\n\nexport interface ExtractedMessage {\n id: string;\n defaultMessage: string;\n description: string;\n filePath: string;\n componentName?: string;\n elementType?: string;\n line: number;\n column: number;\n}\n\nexport interface BetterIntlConfig {\n defaultLocale: string;\n locales: string[];\n catalogs: string;\n include: string[];\n exclude: string[];\n fallback?: Record<string, string>;\n}\n\nexport const DEFAULT_CONFIG: BetterIntlConfig = {\n defaultLocale: \"en\",\n locales: [\"en\"],\n catalogs: \"./locales/{locale}.json\",\n include: [\"src/**/*.tsx\", \"src/**/*.jsx\", \"app/**/*.tsx\", \"app/**/*.jsx\"],\n exclude: [\n \"**/*.test.*\",\n \"**/*.spec.*\",\n \"**/*.stories.*\",\n \"**/node_modules/**\",\n ],\n};\n"]}
1
+ {"version":3,"sources":["../src/catalog.ts","../src/hash.ts","../src/interpolate.ts","../src/locale.ts","../src/runtime.ts","../src/types.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Message catalog — stores and resolves translations per locale.\n */\n\nexport interface MessageDescriptor {\n id: string;\n defaultMessage: string;\n description?: string;\n}\n\nexport type Messages = Record<string, string>;\nexport type CatalogData = Record<string, Messages>;\n\nexport class Catalog {\n private data: CatalogData = {};\n private defaultLocale: string;\n\n constructor(defaultLocale: string = \"en\") {\n this.defaultLocale = defaultLocale;\n }\n\n load(locale: string, messages: Messages): void {\n this.data[locale] = { ...this.data[locale], ...messages };\n }\n\n resolve(id: string, locale: string, defaultMessage?: string): string {\n return (\n this.data[locale]?.[id] ??\n this.data[this.defaultLocale]?.[id] ??\n defaultMessage ??\n id\n );\n }\n\n has(id: string, locale: string): boolean {\n return id in (this.data[locale] ?? {});\n }\n\n getLocales(): string[] {\n return Object.keys(this.data);\n }\n\n getMessages(locale: string): Messages | undefined {\n return this.data[locale];\n }\n\n getAllIds(): Set<string> {\n const ids = new Set<string>();\n for (const messages of Object.values(this.data)) {\n for (const id of Object.keys(messages)) {\n ids.add(id);\n }\n }\n return ids;\n }\n\n toJSON(): CatalogData {\n return structuredClone(this.data);\n }\n}\n","/**\n * Stable hash generation for message IDs.\n *\n * Produces a short, deterministic hash from the message text,\n * file path, and component context. Uses FNV-1a for speed and\n * low collision rate at short lengths.\n */\n\nfunction fnv1a(input: string): number {\n let hash = 0x811c9dc5; // FNV offset basis\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n hash = (hash * 0x01000193) >>> 0; // FNV prime, keep as uint32\n }\n return hash;\n}\n\nexport interface HashInput {\n text: string;\n filePath: string;\n context?: string;\n}\n\n/**\n * Generate a stable, short message ID.\n *\n * Uses only the file name (not directory path) to avoid hash\n * mismatches between different tools (CLI extract vs build loader)\n * that may resolve paths differently.\n *\n * @example\n * generateId({ text: \"Hello world\", filePath: \"src/Hero.tsx\", context: \"Hero\" })\n * // => \"a1b2c3d\"\n */\nexport function generateId(input: HashInput): string {\n // Use only the file name — immune to path resolution differences\n const fileName =\n input.filePath.replace(/\\\\/g, \"/\").split(\"/\").pop() ?? input.filePath;\n const raw = `${fileName}::${input.context ?? \"\"}::${input.text}`;\n const hash = fnv1a(raw);\n return hash.toString(36).padStart(7, \"0\").slice(0, 7);\n}\n","/**\n * ICU-lite interpolation and pluralization.\n *\n * Supports:\n * - Simple variables: \"Hello {name}\"\n * - Plural: \"{count, plural, one {# item} other {# items}}\"\n * - Select: \"{gender, select, male {He} female {She} other {They}}\"\n */\n\nexport type InterpolationValues = Record<string, string | number>;\n\nconst VARIABLE_RE = /\\{(\\w+)\\}/g;\nconst PLURAL_RE = /\\{(\\w+),\\s*plural,\\s*((?:\\w+\\s*\\{[^}]*\\}\\s*)+)\\}/g;\nconst SELECT_RE = /\\{(\\w+),\\s*select,\\s*((?:\\w+\\s*\\{[^}]*\\}\\s*)+)\\}/g;\nconst BRANCH_RE = /(\\w+)\\s*\\{([^}]*)\\}/g;\n\nfunction parseBranches(raw: string): Record<string, string> {\n const branches: Record<string, string> = {};\n let match: RegExpExecArray | null;\n BRANCH_RE.lastIndex = 0;\n // biome-ignore lint/suspicious/noAssignInExpressions: regex exec loop pattern\n while ((match = BRANCH_RE.exec(raw))) {\n branches[match[1]] = match[2];\n }\n return branches;\n}\n\nfunction resolvePlural(\n count: number,\n branches: Record<string, string>,\n): string {\n const key =\n count === 0 ? \"zero\" : count === 1 ? \"one\" : count === 2 ? \"two\" : \"other\";\n\n const template = branches[key] ?? branches.other ?? \"\";\n return template.replace(/#/g, String(count));\n}\n\nexport function interpolate(\n message: string,\n values: InterpolationValues = {},\n): string {\n let result = message;\n\n // Plural\n result = result.replace(PLURAL_RE, (_, name, raw) => {\n const count = Number(values[name] ?? 0);\n return resolvePlural(count, parseBranches(raw));\n });\n\n // Select\n result = result.replace(SELECT_RE, (_, name, raw) => {\n const value = String(values[name] ?? \"\");\n const branches = parseBranches(raw);\n return branches[value] ?? branches.other ?? \"\";\n });\n\n // Simple variables\n result = result.replace(VARIABLE_RE, (_, name) =>\n values[name] != null ? String(values[name]) : `{${name}}`,\n );\n\n return result;\n}\n","/**\n * Locale resolution and negotiation.\n */\n\nexport interface LocaleConfig {\n defaultLocale: string;\n locales: string[];\n fallback?: Record<string, string>;\n}\n\n/**\n * Negotiate the best locale from a list of preferred locales\n * (e.g. from Accept-Language header) against supported locales.\n */\nexport function negotiateLocale(\n preferred: readonly string[],\n supported: readonly string[],\n defaultLocale: string,\n): string {\n const supportedSet = new Set(supported.map((l) => l.toLowerCase()));\n\n for (const pref of preferred) {\n const lower = pref.toLowerCase();\n\n // Exact match\n if (supportedSet.has(lower)) {\n return supported.find((s) => s.toLowerCase() === lower) as string;\n }\n\n // Language-only match (e.g. \"en-US\" -> \"en\")\n const lang = lower.split(\"-\")[0];\n if (supportedSet.has(lang)) {\n return supported.find((s) => s.toLowerCase() === lang) as string;\n }\n\n // Supported locale that starts with the language\n const partial = supported.find((s) =>\n s.toLowerCase().startsWith(`${lang}-`),\n );\n if (partial) return partial;\n }\n\n return defaultLocale;\n}\n\n/**\n * Parse Accept-Language header into ordered locale list.\n */\nexport function parseAcceptLanguage(header: string): string[] {\n return header\n .split(\",\")\n .map((part) => {\n const [locale, q] = part.trim().split(\";q=\");\n return { locale: locale.trim(), quality: q ? parseFloat(q) : 1 };\n })\n .sort((a, b) => b.quality - a.quality)\n .map((entry) => entry.locale);\n}\n","/**\n * Minimal runtime — the `t()` function used at runtime after compilation.\n *\n * The compiler transforms `<h1>Hello world</h1>` into `<h1>{t(\"a1b2c3\")}</h1>`.\n * This module provides the `t` function that resolves the message at runtime.\n */\n\nimport { Catalog, type Messages } from \"./catalog.js\";\nimport { type InterpolationValues, interpolate } from \"./interpolate.js\";\n\nexport interface IntlRuntime {\n locale: string;\n t(id: string, values?: InterpolationValues, defaultMessage?: string): string;\n loadMessages(locale: string, messages: Messages): void;\n setLocale(locale: string): void;\n}\n\nexport function createIntlRuntime(\n defaultLocale: string,\n initialMessages?: Messages,\n): IntlRuntime {\n const catalog = new Catalog(defaultLocale);\n let currentLocale = defaultLocale;\n\n if (initialMessages) {\n catalog.load(defaultLocale, initialMessages);\n }\n\n return {\n get locale() {\n return currentLocale;\n },\n\n t(\n id: string,\n values?: InterpolationValues,\n defaultMessage?: string,\n ): string {\n const raw = catalog.resolve(id, currentLocale, defaultMessage);\n return values ? interpolate(raw, values) : raw;\n },\n\n loadMessages(locale: string, messages: Messages) {\n catalog.load(locale, messages);\n },\n\n setLocale(locale: string) {\n currentLocale = locale;\n },\n };\n}\n","/**\n * Shared types for the better-intl ecosystem.\n */\n\nexport interface ExtractedMessage {\n id: string;\n defaultMessage: string;\n description: string;\n filePath: string;\n componentName?: string;\n elementType?: string;\n line: number;\n column: number;\n}\n\nexport interface BetterIntlConfig {\n defaultLocale: string;\n locales: string[];\n catalogs: string;\n include: string[];\n exclude: string[];\n fallback?: Record<string, string>;\n}\n\nexport const DEFAULT_CONFIG: BetterIntlConfig = {\n defaultLocale: \"en\",\n locales: [\"en\"],\n catalogs: \"./locales/{locale}.json\",\n include: [\"src/**/*.tsx\", \"src/**/*.jsx\", \"app/**/*.tsx\", \"app/**/*.jsx\"],\n exclude: [\n \"**/*.test.*\",\n \"**/*.spec.*\",\n \"**/*.stories.*\",\n \"**/node_modules/**\",\n ],\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-intl/core",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Core runtime for better-intl — hash, catalog, interpolation, pluralization",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",