glotfile 1.0.0 → 1.0.1

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.
@@ -1807,6 +1807,71 @@ var init_vue_i18n_json = __esm({
1807
1807
  }
1808
1808
  });
1809
1809
 
1810
+ // src/server/adapters/next-intl-json.ts
1811
+ var DEFAULT_LOCALE_CASE8, nextIntlJson;
1812
+ var init_next_intl_json = __esm({
1813
+ "src/server/adapters/next-intl-json.ts"() {
1814
+ "use strict";
1815
+ init_adapters();
1816
+ init_shared();
1817
+ init_options();
1818
+ init_format();
1819
+ init_plurals();
1820
+ DEFAULT_LOCALE_CASE8 = "lower-hyphen";
1821
+ nextIntlJson = {
1822
+ name: "next-intl-json",
1823
+ capabilities: {
1824
+ plural: "native",
1825
+ select: "native",
1826
+ nesting: "both",
1827
+ metadata: false,
1828
+ placeholderStyle: "icu",
1829
+ fileGrouping: "per-locale"
1830
+ },
1831
+ defaultLocaleCase: DEFAULT_LOCALE_CASE8,
1832
+ export(state, output) {
1833
+ const files = [];
1834
+ const warnings = [];
1835
+ warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE8));
1836
+ const { indent, finalNewline } = resolveFormat(state, output);
1837
+ const fmt = { indent, sortKeys: true, finalNewline };
1838
+ const emptyAs = resolveEmptyAs(output, "omit");
1839
+ const flatOutput = output.style === "flat";
1840
+ for (const locale of state.config.locales) {
1841
+ const flat = {};
1842
+ for (const [key, entry] of Object.entries(state.keys)) {
1843
+ if (entry.plural) {
1844
+ const forms = resolveForms(entry, locale, state.config.sourceLocale, emptyAs);
1845
+ if (!forms) continue;
1846
+ flat[key] = formsToIcu(entry.plural.arg, forms);
1847
+ } else {
1848
+ const raw = resolveScalar(entry, locale, state.config.sourceLocale, emptyAs);
1849
+ if (raw === null) continue;
1850
+ flat[key] = raw;
1851
+ }
1852
+ }
1853
+ let payload = flat;
1854
+ if (!flatOutput) {
1855
+ const { tree, collisions } = nestKeys(flat);
1856
+ for (const key of collisions) {
1857
+ warnings.push({
1858
+ code: "key-collision",
1859
+ key,
1860
+ locale,
1861
+ message: "key is both a leaf and a parent; dropped from nested output"
1862
+ });
1863
+ }
1864
+ payload = tree;
1865
+ }
1866
+ files.push({ path: resolvePath(output.path, resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE8)), contents: serializeJson(payload, fmt) });
1867
+ }
1868
+ files.sort((a, b) => a.path.localeCompare(b.path));
1869
+ return { files, warnings };
1870
+ }
1871
+ };
1872
+ }
1873
+ });
1874
+
1810
1875
  // src/server/adapters/angular-xliff.ts
1811
1876
  function xmlEscape2(s) {
1812
1877
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -1862,7 +1927,7 @@ function renderEmbeddedIcu(value) {
1862
1927
  function renderScalar(value, ids, placeholders) {
1863
1928
  return isIcuPluralOrSelect(value) ? renderEmbeddedIcu(value) : renderInterpolations(value, ids, placeholders);
1864
1929
  }
1865
- var DEFAULT_LOCALE_CASE8, angularXliff;
1930
+ var DEFAULT_LOCALE_CASE9, angularXliff;
1866
1931
  var init_angular_xliff = __esm({
1867
1932
  "src/server/adapters/angular-xliff.ts"() {
1868
1933
  "use strict";
@@ -1870,7 +1935,7 @@ var init_angular_xliff = __esm({
1870
1935
  init_options();
1871
1936
  init_placeholders();
1872
1937
  init_schema();
1873
- DEFAULT_LOCALE_CASE8 = "bcp47-hyphen";
1938
+ DEFAULT_LOCALE_CASE9 = "bcp47-hyphen";
1874
1939
  angularXliff = {
1875
1940
  name: "angular-xliff",
1876
1941
  capabilities: {
@@ -1881,18 +1946,18 @@ var init_angular_xliff = __esm({
1881
1946
  placeholderStyle: "icu",
1882
1947
  fileGrouping: "per-locale"
1883
1948
  },
1884
- defaultLocaleCase: DEFAULT_LOCALE_CASE8,
1949
+ defaultLocaleCase: DEFAULT_LOCALE_CASE9,
1885
1950
  export(state, output) {
1886
1951
  const files = [];
1887
1952
  const warnings = [];
1888
- warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE8));
1953
+ warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE9));
1889
1954
  const sourceLocale = state.config.sourceLocale;
1890
- const sourceToken = resolveLocaleToken(output, sourceLocale, DEFAULT_LOCALE_CASE8);
1955
+ const sourceToken = resolveLocaleToken(output, sourceLocale, DEFAULT_LOCALE_CASE9);
1891
1956
  const emptyAs = resolveEmptyAs(output, "source");
1892
1957
  const keys = Object.keys(state.keys).sort();
1893
1958
  for (const locale of state.config.locales) {
1894
1959
  if (output.skipSourceLocale && locale === sourceLocale) continue;
1895
- const token = resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE8);
1960
+ const token = resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE9);
1896
1961
  const units = [];
1897
1962
  for (const key of keys) {
1898
1963
  const entry = state.keys[key];
@@ -1957,7 +2022,7 @@ function yamlMap(node, indent, level) {
1957
2022
  }
1958
2023
  return lines;
1959
2024
  }
1960
- var RESERVED_KEYS, DEFAULT_LOCALE_CASE9, railsYaml;
2025
+ var RESERVED_KEYS, DEFAULT_LOCALE_CASE10, railsYaml;
1961
2026
  var init_rails_yaml = __esm({
1962
2027
  "src/server/adapters/rails-yaml.ts"() {
1963
2028
  "use strict";
@@ -1967,7 +2032,7 @@ var init_rails_yaml = __esm({
1967
2032
  init_placeholders();
1968
2033
  init_schema();
1969
2034
  RESERVED_KEYS = /* @__PURE__ */ new Set(["true", "false", "yes", "no", "on", "off", "null", "y", "n"]);
1970
- DEFAULT_LOCALE_CASE9 = "bcp47-hyphen";
2035
+ DEFAULT_LOCALE_CASE10 = "bcp47-hyphen";
1971
2036
  railsYaml = {
1972
2037
  name: "rails-yaml",
1973
2038
  capabilities: {
@@ -1978,10 +2043,10 @@ var init_rails_yaml = __esm({
1978
2043
  placeholderStyle: "named",
1979
2044
  fileGrouping: "per-locale"
1980
2045
  },
1981
- defaultLocaleCase: DEFAULT_LOCALE_CASE9,
2046
+ defaultLocaleCase: DEFAULT_LOCALE_CASE10,
1982
2047
  export(state, output) {
1983
2048
  const warnings = [];
1984
- warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE9));
2049
+ warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE10));
1985
2050
  const { indent, finalNewline } = resolveFormat(state, output);
1986
2051
  const emptyAs = resolveEmptyAs(output, "omit");
1987
2052
  const files = [];
@@ -2013,7 +2078,7 @@ var init_rails_yaml = __esm({
2013
2078
  for (const c of collisions) {
2014
2079
  warnings.push({ code: "key-collision", key: c, locale, message: "key is both a leaf and a parent; dropped from nested output" });
2015
2080
  }
2016
- const token = resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE9);
2081
+ const token = resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE10);
2017
2082
  const body = [`${yamlKey(token)}:`, ...yamlMap(nested, indent, 1)].join("\n");
2018
2083
  files.push({ path: resolvePath(output.path, token), contents: finalNewline ? body + "\n" : body });
2019
2084
  }
@@ -2057,6 +2122,7 @@ function getRegistry() {
2057
2122
  [appleStringsdict.name]: appleStringsdict,
2058
2123
  [appleStrings.name]: appleStrings,
2059
2124
  [vueI18nJson.name]: vueI18nJson,
2125
+ [nextIntlJson.name]: nextIntlJson,
2060
2126
  [angularXliff.name]: angularXliff,
2061
2127
  [railsYaml.name]: railsYaml
2062
2128
  };
@@ -2078,6 +2144,7 @@ var init_adapters = __esm({
2078
2144
  init_apple_stringsdict();
2079
2145
  init_apple_strings();
2080
2146
  init_vue_i18n_json();
2147
+ init_next_intl_json();
2081
2148
  init_angular_xliff();
2082
2149
  init_rails_yaml();
2083
2150
  }
@@ -4230,6 +4297,69 @@ function customPatterns(opts) {
4230
4297
  }
4231
4298
  return out;
4232
4299
  }
4300
+ function isNextIntlFile(content) {
4301
+ return NEXT_INTL_IMPORT.test(content);
4302
+ }
4303
+ function nextIntlBindings(content) {
4304
+ const out = [];
4305
+ for (const m of content.matchAll(NI_BIND)) out.push({ name: m[1], ns: m[2] ?? "", index: m.index });
4306
+ for (const m of content.matchAll(NI_BIND_OBJ)) out.push({ name: m[1], ns: m[2], index: m.index });
4307
+ return out;
4308
+ }
4309
+ function nsForBindingAt(bindings, name, index) {
4310
+ let best = null;
4311
+ for (const b of bindings) {
4312
+ if (b.name === name && b.index < index && (!best || b.index > best.index)) best = b;
4313
+ }
4314
+ return best ? best.ns : null;
4315
+ }
4316
+ function joinKey(ns, rel) {
4317
+ return ns ? `${ns}.${rel}` : rel;
4318
+ }
4319
+ function uniqueBindingNames(bindings) {
4320
+ return [...new Set(bindings.map((b) => b.name))];
4321
+ }
4322
+ function nextIntlRefMatches(content) {
4323
+ const bindings = nextIntlBindings(content);
4324
+ const out = [];
4325
+ for (const name of uniqueBindingNames(bindings)) {
4326
+ const re = new RegExp(
4327
+ `\\b${escapeRe2(name)}${NI_METHOD}\\s*\\(\\s*(?:'([^'\\n]+)'|"([^"\\n]+)"|\`([^\`$\\n]+)\`)`,
4328
+ "g"
4329
+ );
4330
+ let m;
4331
+ while ((m = re.exec(content)) !== null) {
4332
+ const ns = nsForBindingAt(bindings, name, m.index);
4333
+ if (ns === null) continue;
4334
+ out.push({ key: joinKey(ns, m[1] ?? m[2] ?? m[3]), index: m.index });
4335
+ }
4336
+ }
4337
+ return out;
4338
+ }
4339
+ function nextIntlPrefixMatches(content) {
4340
+ const bindings = nextIntlBindings(content);
4341
+ const out = [];
4342
+ for (const name of uniqueBindingNames(bindings)) {
4343
+ const ev = escapeRe2(name);
4344
+ const headConcat = new RegExp(`\\b${ev}${NI_METHOD}\\s*\\(\\s*(?:'([^'\\n]*)'|"([^"\\n]*)")\\s*\\+`, "g");
4345
+ const headTemplate = new RegExp(`\\b${ev}${NI_METHOD}\\s*\\(\\s*\`([^\`$\\n]*)\\$\\{`, "g");
4346
+ const dynamicArg = new RegExp(`\\b${ev}${NI_METHOD}\\s*\\(\\s*[A-Za-z_$][\\w$.]*\\s*[),]`, "g");
4347
+ let m;
4348
+ for (const re of [headConcat, headTemplate]) {
4349
+ while ((m = re.exec(content)) !== null) {
4350
+ const ns = nsForBindingAt(bindings, name, m.index);
4351
+ if (ns === null) continue;
4352
+ out.push({ prefix: joinKey(ns, m[1] ?? m[2] ?? ""), index: m.index });
4353
+ }
4354
+ }
4355
+ while ((m = dynamicArg.exec(content)) !== null) {
4356
+ const ns = nsForBindingAt(bindings, name, m.index);
4357
+ if (!ns) continue;
4358
+ out.push({ prefix: `${ns}.`, index: m.index });
4359
+ }
4360
+ }
4361
+ return out;
4362
+ }
4233
4363
  function lineStartOffsets(content) {
4234
4364
  const starts = [0];
4235
4365
  let idx = content.indexOf("\n");
@@ -4250,49 +4380,56 @@ function offsetToLineCol(starts, offset) {
4250
4380
  return { line: lo + 1, col: offset - starts[lo] + 1 };
4251
4381
  }
4252
4382
  function extractRefs(content, scanner, opts) {
4253
- const base = scanner === "flutter" ? flutterPatterns(content, opts) : PATTERNS[scanner] ?? [];
4254
- const patterns = [...base, ...customPatterns(opts)];
4255
- if (patterns.length === 0) return [];
4383
+ const useNextIntl = scanner === "next-intl" || scanner === "js-i18n" && isNextIntlFile(content);
4384
+ const effScanner = useNextIntl ? "next-intl" : scanner;
4256
4385
  const starts = lineStartOffsets(content);
4257
4386
  const result = [];
4258
4387
  const seen = /* @__PURE__ */ new Set();
4259
- for (const pattern of patterns) {
4260
- const re = new RegExp(pattern.source, "g");
4261
- let m;
4262
- while ((m = re.exec(content)) !== null) {
4263
- if (m.index === re.lastIndex) re.lastIndex++;
4264
- const key = m[1];
4265
- const { line, col } = offsetToLineCol(starts, m.index);
4266
- const dedup = `${line}:${col}:${key}`;
4267
- if (!seen.has(dedup)) {
4268
- seen.add(dedup);
4269
- result.push({ key, line, col, scanner });
4270
- }
4388
+ const push = (key, index) => {
4389
+ const { line, col } = offsetToLineCol(starts, index);
4390
+ const dedup = `${line}:${col}:${key}`;
4391
+ if (!seen.has(dedup)) {
4392
+ seen.add(dedup);
4393
+ result.push({ key, line, col, scanner: effScanner });
4271
4394
  }
4395
+ };
4396
+ if (useNextIntl) {
4397
+ for (const r of nextIntlRefMatches(content)) push(r.key, r.index);
4398
+ } else {
4399
+ const base = scanner === "flutter" ? flutterPatterns(content, opts) : PATTERNS[scanner] ?? [];
4400
+ for (const pattern of base) eachMatch(content, pattern, push);
4272
4401
  }
4402
+ for (const pattern of customPatterns(opts)) eachMatch(content, pattern, push);
4273
4403
  result.sort((a, b) => a.line - b.line || a.col - b.col);
4274
4404
  return result;
4275
4405
  }
4406
+ function eachMatch(content, pattern, fn) {
4407
+ const re = new RegExp(pattern.source, "g");
4408
+ let m;
4409
+ while ((m = re.exec(content)) !== null) {
4410
+ if (m.index === re.lastIndex) re.lastIndex++;
4411
+ fn(m[1], m.index);
4412
+ }
4413
+ }
4276
4414
  function extractPrefixes(content, scanner) {
4277
- const patterns = PREFIX_PATTERNS[scanner];
4278
- if (!patterns) return [];
4415
+ const useNextIntl = scanner === "next-intl" || scanner === "js-i18n" && isNextIntlFile(content);
4416
+ const effScanner = useNextIntl ? "next-intl" : scanner;
4279
4417
  const starts = lineStartOffsets(content);
4280
4418
  const result = [];
4281
4419
  const seen = /* @__PURE__ */ new Set();
4282
- for (const pattern of patterns) {
4283
- const re = new RegExp(pattern.source, "g");
4284
- let m;
4285
- while ((m = re.exec(content)) !== null) {
4286
- if (m.index === re.lastIndex) re.lastIndex++;
4287
- const prefix = m[1];
4288
- if (!prefix) continue;
4289
- const { line, col } = offsetToLineCol(starts, m.index);
4290
- const dedup = `${line}:${col}:${prefix}`;
4291
- if (!seen.has(dedup)) {
4292
- seen.add(dedup);
4293
- result.push({ prefix, line, col, scanner });
4294
- }
4420
+ const push = (prefix, index) => {
4421
+ if (!prefix) return;
4422
+ const { line, col } = offsetToLineCol(starts, index);
4423
+ const dedup = `${line}:${col}:${prefix}`;
4424
+ if (!seen.has(dedup)) {
4425
+ seen.add(dedup);
4426
+ result.push({ prefix, line, col, scanner: effScanner });
4295
4427
  }
4428
+ };
4429
+ if (useNextIntl) {
4430
+ for (const p of nextIntlPrefixMatches(content)) push(p.prefix, p.index);
4431
+ } else {
4432
+ for (const pattern of PREFIX_PATTERNS[scanner] ?? []) eachMatch(content, pattern, push);
4296
4433
  }
4297
4434
  result.sort((a, b) => a.line - b.line || a.col - b.col);
4298
4435
  return result;
@@ -4404,7 +4541,7 @@ function runScan(projectRoot, opts, existing) {
4404
4541
  saveUsageCache(projectRoot, cache2);
4405
4542
  return cache2;
4406
4543
  }
4407
- var PATTERNS, PREFIX_PATTERNS, CACHE_VERSION, EXT_SCANNER, ALWAYS_EXCLUDE, FLUTTER_ACCESSOR_DEFAULTS, KEY_SHAPE, STRING_LITERALS;
4544
+ var PATTERNS, PREFIX_PATTERNS, CACHE_VERSION, EXT_SCANNER, ALWAYS_EXCLUDE, FLUTTER_ACCESSOR_DEFAULTS, NEXT_INTL_IMPORT, NI_BIND, NI_BIND_OBJ, NI_METHOD, KEY_SHAPE, STRING_LITERALS;
4408
4545
  var init_scanner = __esm({
4409
4546
  "src/server/scanner.ts"() {
4410
4547
  "use strict";
@@ -4475,7 +4612,7 @@ var init_scanner = __esm({
4475
4612
  /(?<!\.)(?<![a-zA-Z0-9_$])\bt\s*\(\s*`([^`$]*)\$\{/g
4476
4613
  ]
4477
4614
  };
4478
- CACHE_VERSION = 7;
4615
+ CACHE_VERSION = 8;
4479
4616
  EXT_SCANNER = {
4480
4617
  ".php": "laravel",
4481
4618
  ".vue": "js-i18n",
@@ -4509,6 +4646,10 @@ var init_scanner = __esm({
4509
4646
  "__pycache__"
4510
4647
  ]);
4511
4648
  FLUTTER_ACCESSOR_DEFAULTS = ["l10n", "loc", "localizations", "translations"];
4649
+ NEXT_INTL_IMPORT = /(?:from\s*|require\(\s*)['"]next-intl(?:\/[\w-]+)?['"]/;
4650
+ NI_BIND = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:await\s+)?(?:useTranslations|getTranslations)\s*\(\s*(?:['"]([^'"]*)['"])?\s*\)/g;
4651
+ NI_BIND_OBJ = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:await\s+)?getTranslations\s*\(\s*\{[^}]*?\bnamespace\s*:\s*['"]([^'"]+)['"][^}]*?\}\s*\)/g;
4652
+ NI_METHOD = "(?:\\.(?:rich|markup|raw|has))?";
4512
4653
  KEY_SHAPE = /^[A-Za-z0-9_][A-Za-z0-9_/-]*(?:\.(?:[A-Za-z0-9_-]+|%[sd]))+\.?$/;
4513
4654
  STRING_LITERALS = [
4514
4655
  /'([^'\\\n]+)'/g,
@@ -4569,6 +4710,44 @@ function detectVue(root, forced = false) {
4569
4710
  }
4570
4711
  return null;
4571
4712
  }
4713
+ function hasNextIntlSignal(root) {
4714
+ if (NEXT_INTL_CONFIG_CANDIDATES.some((rel) => existsSync11(join6(root, rel)))) return true;
4715
+ try {
4716
+ const pkg = JSON.parse(readFileSync12(join6(root, "package.json"), "utf8"));
4717
+ if (pkg.dependencies?.["next-intl"] || pkg.devDependencies?.["next-intl"]) return true;
4718
+ } catch {
4719
+ }
4720
+ return false;
4721
+ }
4722
+ function nextIntlDefaultLocale(root) {
4723
+ for (const rel of NEXT_INTL_ROUTING_CANDIDATES) {
4724
+ try {
4725
+ const m = readFileSync12(join6(root, rel), "utf8").match(/defaultLocale\s*:\s*['"]([^'"]+)['"]/);
4726
+ if (m) return m[1];
4727
+ } catch {
4728
+ }
4729
+ }
4730
+ return void 0;
4731
+ }
4732
+ function detectNextIntl(root, forced = false) {
4733
+ if (!forced && !hasNextIntlSignal(root)) return null;
4734
+ for (const rel of NEXT_INTL_DIR_CANDIDATES) {
4735
+ const localeRoot = join6(root, rel);
4736
+ if (!safeIsDir(localeRoot)) continue;
4737
+ const locales = readdirSync4(localeRoot).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).filter((l) => LOCALE_RE.test(l));
4738
+ if (locales.length === 0) continue;
4739
+ const def = nextIntlDefaultLocale(root);
4740
+ const sourceLocale = def && locales.includes(def) ? def : pickSource(locales, (loc) => {
4741
+ try {
4742
+ return statSync4(join6(localeRoot, `${loc}.json`)).size;
4743
+ } catch {
4744
+ return 0;
4745
+ }
4746
+ });
4747
+ return { format: "next-intl-json", localeRoot, locales, sourceLocale };
4748
+ }
4749
+ return null;
4750
+ }
4572
4751
  function detectArb(root) {
4573
4752
  for (const rel of ["lib/l10n", "l10n", "lib/src/l10n"]) {
4574
4753
  const localeRoot = join6(root, rel);
@@ -4723,17 +4902,21 @@ function detect(root, formatOverride) {
4723
4902
  }
4724
4903
  return null;
4725
4904
  }
4726
- var LOCALE_RE, VUE_DIR_CANDIDATES, ANGULAR_DIR_CANDIDATES, I18NEXT_DIR_CANDIDATES, GETTEXT_DIR_CANDIDATES, DETECTORS, BY_FORMAT;
4905
+ var LOCALE_RE, VUE_DIR_CANDIDATES, NEXT_INTL_CONFIG_CANDIDATES, NEXT_INTL_ROUTING_CANDIDATES, NEXT_INTL_DIR_CANDIDATES, ANGULAR_DIR_CANDIDATES, I18NEXT_DIR_CANDIDATES, GETTEXT_DIR_CANDIDATES, DETECTORS, BY_FORMAT;
4727
4906
  var init_detect = __esm({
4728
4907
  "src/server/import/detect.ts"() {
4729
4908
  "use strict";
4730
4909
  LOCALE_RE = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
4731
4910
  VUE_DIR_CANDIDATES = ["src/locale", "src/locales", "src/i18n/locales", "locales", "lang"];
4911
+ NEXT_INTL_CONFIG_CANDIDATES = ["src/i18n/request.ts", "i18n/request.ts", "src/i18n/request.js", "i18n/request.js"];
4912
+ NEXT_INTL_ROUTING_CANDIDATES = ["src/i18n/routing.ts", "i18n/routing.ts", "src/i18n/routing.js", "i18n/routing.js"];
4913
+ NEXT_INTL_DIR_CANDIDATES = ["messages", "src/messages", "locales", "src/locales", "src/i18n/messages"];
4732
4914
  ANGULAR_DIR_CANDIDATES = [".", "src/locale", "src/locales", "src/i18n", "locale", "locales", "i18n", "translations"];
4733
4915
  I18NEXT_DIR_CANDIDATES = ["public/locales", "static/locales", "locales", "src/locales", "src/i18n/locales"];
4734
4916
  GETTEXT_DIR_CANDIDATES = ["locale", "locales", "po", "translations"];
4735
4917
  DETECTORS = [
4736
4918
  detectLaravel,
4919
+ detectNextIntl,
4737
4920
  detectVue,
4738
4921
  detectArb,
4739
4922
  detectApple,
@@ -4745,6 +4928,7 @@ var init_detect = __esm({
4745
4928
  ];
4746
4929
  BY_FORMAT = {
4747
4930
  "laravel-php": detectLaravel,
4931
+ "next-intl-json": (root) => detectNextIntl(root, true),
4748
4932
  "vue-i18n-json": (root) => detectVue(root, true),
4749
4933
  "flutter-arb": detectArb,
4750
4934
  "apple-strings": detectApple,
@@ -4824,6 +5008,44 @@ var init_vue_i18n_json2 = __esm({
4824
5008
  }
4825
5009
  });
4826
5010
 
5011
+ // src/server/import/parsers/next-intl-json.ts
5012
+ import { readdirSync as readdirSync6, readFileSync as readFileSync14 } from "fs";
5013
+ import { join as join8 } from "path";
5014
+ var LOCALE_RE3, nextIntlJson2;
5015
+ var init_next_intl_json2 = __esm({
5016
+ "src/server/import/parsers/next-intl-json.ts"() {
5017
+ "use strict";
5018
+ init_flatten();
5019
+ LOCALE_RE3 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
5020
+ nextIntlJson2 = {
5021
+ name: "next-intl-json",
5022
+ parse(localeRoot, opts) {
5023
+ const warnings = [];
5024
+ const keys = {};
5025
+ const locales = [];
5026
+ for (const file of readdirSync6(localeRoot).sort()) {
5027
+ if (!file.endsWith(".json")) continue;
5028
+ const locale = file.slice(0, -".json".length);
5029
+ if (!LOCALE_RE3.test(locale)) continue;
5030
+ if (opts?.locales && !opts.locales.includes(locale)) continue;
5031
+ let data;
5032
+ try {
5033
+ data = JSON.parse(readFileSync14(join8(localeRoot, file), "utf8"));
5034
+ } catch (e) {
5035
+ warnings.push(`next-intl-json: failed to parse ${file}: ${e.message}`);
5036
+ continue;
5037
+ }
5038
+ if (!locales.includes(locale)) locales.push(locale);
5039
+ for (const [key, value] of Object.entries(flattenObject(data, "", warnings))) {
5040
+ (keys[key] ??= { values: {} }).values[locale] = value;
5041
+ }
5042
+ }
5043
+ return { locales, keys, warnings };
5044
+ }
5045
+ };
5046
+ }
5047
+ });
5048
+
4827
5049
  // src/server/import/placeholders.ts
4828
5050
  function markBareBracesLiteral(value) {
4829
5051
  return value.replace(/(?<!%)\{(\w+)\}/g, "'{$1}'");
@@ -4841,17 +5063,17 @@ var init_placeholders2 = __esm({
4841
5063
  });
4842
5064
 
4843
5065
  // src/server/import/parsers/laravel-php.ts
4844
- import { readdirSync as readdirSync6, statSync as statSync5 } from "fs";
4845
- import { join as join8, relative as relative2 } from "path";
5066
+ import { readdirSync as readdirSync7, statSync as statSync5 } from "fs";
5067
+ import { join as join9, relative as relative2 } from "path";
4846
5068
  import { execFileSync } from "child_process";
4847
5069
  function listDirs2(dir) {
4848
- return readdirSync6(dir).filter((e) => statSync5(join8(dir, e)).isDirectory());
5070
+ return readdirSync7(dir).filter((e) => statSync5(join9(dir, e)).isDirectory());
4849
5071
  }
4850
5072
  function listPhpFiles(dir) {
4851
5073
  const out = [];
4852
5074
  const walk = (d) => {
4853
- for (const e of readdirSync6(d)) {
4854
- const full = join8(d, e);
5075
+ for (const e of readdirSync7(d)) {
5076
+ const full = join9(d, e);
4855
5077
  if (statSync5(full).isDirectory()) walk(full);
4856
5078
  else if (e.endsWith(".php")) out.push(full);
4857
5079
  }
@@ -4894,7 +5116,7 @@ var init_laravel_php2 = __esm({
4894
5116
  for (const locale of listDirs2(localeRoot).sort()) {
4895
5117
  if (locale === "vendor") continue;
4896
5118
  if (opts?.locales && !opts.locales.includes(locale)) continue;
4897
- const localeDir = join8(localeRoot, locale);
5119
+ const localeDir = join9(localeRoot, locale);
4898
5120
  locales.push(locale);
4899
5121
  for (const file of listPhpFiles(localeDir)) {
4900
5122
  const group = relative2(localeDir, file).replace(/\\/g, "/").replace(/\.php$/, "");
@@ -4919,14 +5141,14 @@ var init_laravel_php2 = __esm({
4919
5141
  });
4920
5142
 
4921
5143
  // src/server/import/parsers/flutter-arb.ts
4922
- import { readdirSync as readdirSync7, readFileSync as readFileSync14 } from "fs";
4923
- import { join as join9 } from "path";
5144
+ import { readdirSync as readdirSync8, readFileSync as readFileSync15 } from "fs";
5145
+ import { join as join10 } from "path";
4924
5146
  function localeFromArbName(file) {
4925
5147
  const m = file.match(/^(.+)\.arb$/);
4926
5148
  if (!m) return null;
4927
5149
  let locale = m[1];
4928
5150
  if (locale.startsWith("app_")) locale = locale.slice(4);
4929
- return LOCALE_RE3.test(locale) ? locale : null;
5151
+ return LOCALE_RE4.test(locale) ? locale : null;
4930
5152
  }
4931
5153
  function placeholderMeta(raw) {
4932
5154
  if (!raw || typeof raw !== "object") return void 0;
@@ -4942,25 +5164,25 @@ function placeholderMeta(raw) {
4942
5164
  }
4943
5165
  return Object.keys(out).length ? out : void 0;
4944
5166
  }
4945
- var LOCALE_RE3, flutterArb2;
5167
+ var LOCALE_RE4, flutterArb2;
4946
5168
  var init_flutter_arb2 = __esm({
4947
5169
  "src/server/import/parsers/flutter-arb.ts"() {
4948
5170
  "use strict";
4949
- LOCALE_RE3 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
5171
+ LOCALE_RE4 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
4950
5172
  flutterArb2 = {
4951
5173
  name: "flutter-arb",
4952
5174
  parse(localeRoot, opts) {
4953
5175
  const warnings = [];
4954
5176
  const keys = {};
4955
5177
  const locales = [];
4956
- for (const file of readdirSync7(localeRoot).sort()) {
5178
+ for (const file of readdirSync8(localeRoot).sort()) {
4957
5179
  if (!file.endsWith(".arb")) continue;
4958
5180
  const locale = localeFromArbName(file);
4959
5181
  if (!locale) continue;
4960
5182
  if (opts?.locales && !opts.locales.includes(locale)) continue;
4961
5183
  let data;
4962
5184
  try {
4963
- data = JSON.parse(readFileSync14(join9(localeRoot, file), "utf8"));
5185
+ data = JSON.parse(readFileSync15(join10(localeRoot, file), "utf8"));
4964
5186
  } catch (e) {
4965
5187
  warnings.push(`flutter-arb: failed to parse ${file}: ${e.message}`);
4966
5188
  continue;
@@ -4987,12 +5209,12 @@ var init_flutter_arb2 = __esm({
4987
5209
  });
4988
5210
 
4989
5211
  // src/server/import/parsers/apple-strings.ts
4990
- import { readdirSync as readdirSync8, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
4991
- import { join as join10 } from "path";
5212
+ import { readdirSync as readdirSync9, readFileSync as readFileSync16, statSync as statSync6 } from "fs";
5213
+ import { join as join11 } from "path";
4992
5214
  function localeFromLproj(dir) {
4993
5215
  const m = dir.match(/^(.+)\.lproj$/);
4994
5216
  if (!m) return null;
4995
- return LOCALE_RE4.test(m[1]) ? m[1] : null;
5217
+ return LOCALE_RE5.test(m[1]) ? m[1] : null;
4996
5218
  }
4997
5219
  function printfToCanonical(s) {
4998
5220
  return s.replace(/%%/g, "%");
@@ -5092,11 +5314,11 @@ function parseStrings(text, file, warnings) {
5092
5314
  }
5093
5315
  return pairs;
5094
5316
  }
5095
- var LOCALE_RE4, TABLE, appleStrings2;
5317
+ var LOCALE_RE5, TABLE, appleStrings2;
5096
5318
  var init_apple_strings2 = __esm({
5097
5319
  "src/server/import/parsers/apple-strings.ts"() {
5098
5320
  "use strict";
5099
- LOCALE_RE4 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
5321
+ LOCALE_RE5 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
5100
5322
  TABLE = "Localizable.strings";
5101
5323
  appleStrings2 = {
5102
5324
  name: "apple-strings",
@@ -5104,20 +5326,20 @@ var init_apple_strings2 = __esm({
5104
5326
  const warnings = [];
5105
5327
  const keys = {};
5106
5328
  const locales = [];
5107
- for (const dir of readdirSync8(localeRoot).sort()) {
5329
+ for (const dir of readdirSync9(localeRoot).sort()) {
5108
5330
  const locale = localeFromLproj(dir);
5109
5331
  if (!locale) continue;
5110
5332
  if (opts?.locales && !opts.locales.includes(locale)) continue;
5111
- const file = join10(localeRoot, dir, TABLE);
5333
+ const file = join11(localeRoot, dir, TABLE);
5112
5334
  let text;
5113
5335
  try {
5114
5336
  if (!statSync6(file).isFile()) continue;
5115
- text = readFileSync15(file, "utf8");
5337
+ text = readFileSync16(file, "utf8");
5116
5338
  } catch {
5117
5339
  continue;
5118
5340
  }
5119
5341
  locales.push(locale);
5120
- const others = readdirSync8(join10(localeRoot, dir)).filter((f) => f.endsWith(".strings") && f !== TABLE);
5342
+ const others = readdirSync9(join11(localeRoot, dir)).filter((f) => f.endsWith(".strings") && f !== TABLE);
5121
5343
  if (others.length) {
5122
5344
  warnings.push(`apple-strings: ${dir} has other .strings tables (${others.join(", ")}); only ${TABLE} is imported`);
5123
5345
  }
@@ -5132,8 +5354,8 @@ var init_apple_strings2 = __esm({
5132
5354
  });
5133
5355
 
5134
5356
  // src/server/import/parsers/angular-xliff.ts
5135
- import { readdirSync as readdirSync9, readFileSync as readFileSync16 } from "fs";
5136
- import { join as join11 } from "path";
5357
+ import { readdirSync as readdirSync10, readFileSync as readFileSync17 } from "fs";
5358
+ import { join as join12 } from "path";
5137
5359
  function decodeEntities(s) {
5138
5360
  return s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d))).replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
5139
5361
  }
@@ -5182,11 +5404,11 @@ function decodeInline(raw, addMeta) {
5182
5404
  }
5183
5405
  return out + decodeEntities(raw.slice(last));
5184
5406
  }
5185
- var LOCALE_RE5, FILE_RE, ANGULAR_CONVENTION_ID, angularXliff2;
5407
+ var LOCALE_RE6, FILE_RE, ANGULAR_CONVENTION_ID, angularXliff2;
5186
5408
  var init_angular_xliff2 = __esm({
5187
5409
  "src/server/import/parsers/angular-xliff.ts"() {
5188
5410
  "use strict";
5189
- LOCALE_RE5 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
5411
+ LOCALE_RE6 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
5190
5412
  FILE_RE = /^messages(?:\.(.+))?\.xlf$/;
5191
5413
  ANGULAR_CONVENTION_ID = /^[A-Z][A-Z0-9_]*$/;
5192
5414
  angularXliff2 = {
@@ -5198,13 +5420,13 @@ var init_angular_xliff2 = __esm({
5198
5420
  const seen = (loc) => {
5199
5421
  if (!locales.includes(loc)) locales.push(loc);
5200
5422
  };
5201
- const files = readdirSync9(localeRoot).filter((f) => FILE_RE.test(f)).sort((a, b) => (a === "messages.xlf" ? -1 : 0) - (b === "messages.xlf" ? -1 : 0) || a.localeCompare(b));
5423
+ const files = readdirSync10(localeRoot).filter((f) => FILE_RE.test(f)).sort((a, b) => (a === "messages.xlf" ? -1 : 0) - (b === "messages.xlf" ? -1 : 0) || a.localeCompare(b));
5202
5424
  for (const file of files) {
5203
5425
  const fnameLocale = file.match(FILE_RE)[1];
5204
- if (fnameLocale !== void 0 && !LOCALE_RE5.test(fnameLocale)) continue;
5426
+ if (fnameLocale !== void 0 && !LOCALE_RE6.test(fnameLocale)) continue;
5205
5427
  let xml;
5206
5428
  try {
5207
- xml = readFileSync16(join11(localeRoot, file), "utf8");
5429
+ xml = readFileSync17(join12(localeRoot, file), "utf8");
5208
5430
  } catch (e) {
5209
5431
  warnings.push(`angular-xliff: failed to read ${file}: ${e.message}`);
5210
5432
  continue;
@@ -5251,8 +5473,8 @@ var init_angular_xliff2 = __esm({
5251
5473
  });
5252
5474
 
5253
5475
  // src/server/import/parsers/gettext-po.ts
5254
- import { readdirSync as readdirSync10, readFileSync as readFileSync17 } from "fs";
5255
- import { join as join12 } from "path";
5476
+ import { readdirSync as readdirSync11, readFileSync as readFileSync18 } from "fs";
5477
+ import { join as join13 } from "path";
5256
5478
  function unescapePo(s) {
5257
5479
  return s.replace(
5258
5480
  /\\([\\"ntr])/g,
@@ -5337,33 +5559,33 @@ function parseEntries(text) {
5337
5559
  }
5338
5560
  function discoverPoFiles(root) {
5339
5561
  const found = [];
5340
- const entries = readdirSync10(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
5562
+ const entries = readdirSync11(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
5341
5563
  for (const e of entries) {
5342
5564
  if (e.isFile() && e.name.endsWith(".po")) {
5343
5565
  const base = e.name.slice(0, -3);
5344
- found.push({ path: join12(root, e.name), rel: e.name, locale: LOCALE_RE6.test(base) ? base : null });
5345
- } else if (e.isDirectory() && LOCALE_RE6.test(e.name)) {
5346
- for (const sub of [join12(e.name, "LC_MESSAGES"), e.name]) {
5566
+ found.push({ path: join13(root, e.name), rel: e.name, locale: LOCALE_RE7.test(base) ? base : null });
5567
+ } else if (e.isDirectory() && LOCALE_RE7.test(e.name)) {
5568
+ for (const sub of [join13(e.name, "LC_MESSAGES"), e.name]) {
5347
5569
  let names;
5348
5570
  try {
5349
- names = readdirSync10(join12(root, sub)).sort();
5571
+ names = readdirSync11(join13(root, sub)).sort();
5350
5572
  } catch {
5351
5573
  continue;
5352
5574
  }
5353
5575
  for (const f of names) {
5354
- if (f.endsWith(".po")) found.push({ path: join12(root, sub, f), rel: join12(sub, f), locale: e.name });
5576
+ if (f.endsWith(".po")) found.push({ path: join13(root, sub, f), rel: join13(sub, f), locale: e.name });
5355
5577
  }
5356
5578
  }
5357
5579
  }
5358
5580
  }
5359
5581
  return found;
5360
5582
  }
5361
- var LOCALE_RE6, DIRECTIVE_RE, CONT_RE, gettextPo2;
5583
+ var LOCALE_RE7, DIRECTIVE_RE, CONT_RE, gettextPo2;
5362
5584
  var init_gettext_po2 = __esm({
5363
5585
  "src/server/import/parsers/gettext-po.ts"() {
5364
5586
  "use strict";
5365
5587
  init_plurals();
5366
- LOCALE_RE6 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
5588
+ LOCALE_RE7 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
5367
5589
  DIRECTIVE_RE = /^(msgctxt|msgid_plural|msgid|msgstr)(?:\[(\d+)\])?[ \t]+"(.*)"\s*$/;
5368
5590
  CONT_RE = /^[ \t]*"(.*)"\s*$/;
5369
5591
  gettextPo2 = {
@@ -5375,7 +5597,7 @@ var init_gettext_po2 = __esm({
5375
5597
  for (const file of discoverPoFiles(localeRoot)) {
5376
5598
  let entries;
5377
5599
  try {
5378
- entries = parseEntries(readFileSync17(file.path, "utf8"));
5600
+ entries = parseEntries(readFileSync18(file.path, "utf8"));
5379
5601
  } catch (e) {
5380
5602
  warnings.push(`gettext-po: failed to parse ${file.rel}: ${e.message}`);
5381
5603
  continue;
@@ -5422,8 +5644,8 @@ var init_gettext_po2 = __esm({
5422
5644
  });
5423
5645
 
5424
5646
  // src/server/import/parsers/i18next-json.ts
5425
- import { readdirSync as readdirSync11, readFileSync as readFileSync18, statSync as statSync7 } from "fs";
5426
- import { join as join13 } from "path";
5647
+ import { readdirSync as readdirSync12, readFileSync as readFileSync19, statSync as statSync7 } from "fs";
5648
+ import { join as join14 } from "path";
5427
5649
  function safeIsDir2(p) {
5428
5650
  try {
5429
5651
  return statSync7(p).isDirectory();
@@ -5438,7 +5660,7 @@ function fromI18next(value) {
5438
5660
  function ingestFile(path, label, prefix, locale, keys, warnings) {
5439
5661
  let data;
5440
5662
  try {
5441
- data = JSON.parse(readFileSync18(path, "utf8"));
5663
+ data = JSON.parse(readFileSync19(path, "utf8"));
5442
5664
  } catch (e) {
5443
5665
  warnings.push(`i18next-json: failed to parse ${label}: ${e.message}`);
5444
5666
  return false;
@@ -5473,14 +5695,14 @@ function ingestFile(path, label, prefix, locale, keys, warnings) {
5473
5695
  }
5474
5696
  return true;
5475
5697
  }
5476
- var LOCALE_RE7, PLURAL_SUFFIX_RE, PLURAL_ARG, DEFAULT_NAMESPACE, i18nextJson2;
5698
+ var LOCALE_RE8, PLURAL_SUFFIX_RE, PLURAL_ARG, DEFAULT_NAMESPACE, i18nextJson2;
5477
5699
  var init_i18next_json2 = __esm({
5478
5700
  "src/server/import/parsers/i18next-json.ts"() {
5479
5701
  "use strict";
5480
5702
  init_flatten();
5481
5703
  init_plurals();
5482
5704
  init_placeholders();
5483
- LOCALE_RE7 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
5705
+ LOCALE_RE8 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
5484
5706
  PLURAL_SUFFIX_RE = /^(.+)_(zero|one|two|few|many|other)$/;
5485
5707
  PLURAL_ARG = "count";
5486
5708
  DEFAULT_NAMESPACE = "translation";
@@ -5490,22 +5712,22 @@ var init_i18next_json2 = __esm({
5490
5712
  const warnings = [];
5491
5713
  const keys = {};
5492
5714
  const locales = [];
5493
- for (const entry of readdirSync11(localeRoot).sort()) {
5494
- const full = join13(localeRoot, entry);
5715
+ for (const entry of readdirSync12(localeRoot).sort()) {
5716
+ const full = join14(localeRoot, entry);
5495
5717
  if (safeIsDir2(full)) {
5496
- if (!LOCALE_RE7.test(entry)) continue;
5718
+ if (!LOCALE_RE8.test(entry)) continue;
5497
5719
  if (opts?.locales && !opts.locales.includes(entry)) continue;
5498
5720
  let any = false;
5499
- for (const file of readdirSync11(full).sort()) {
5721
+ for (const file of readdirSync12(full).sort()) {
5500
5722
  if (!file.endsWith(".json")) continue;
5501
5723
  const ns = file.slice(0, -".json".length);
5502
5724
  const prefix = ns === DEFAULT_NAMESPACE ? "" : `${ns}.`;
5503
- if (ingestFile(join13(full, file), `${entry}/${file}`, prefix, entry, keys, warnings)) any = true;
5725
+ if (ingestFile(join14(full, file), `${entry}/${file}`, prefix, entry, keys, warnings)) any = true;
5504
5726
  }
5505
5727
  if (any && !locales.includes(entry)) locales.push(entry);
5506
5728
  } else if (entry.endsWith(".json")) {
5507
5729
  const locale = entry.slice(0, -".json".length);
5508
- if (!LOCALE_RE7.test(locale)) continue;
5730
+ if (!LOCALE_RE8.test(locale)) continue;
5509
5731
  if (opts?.locales && !opts.locales.includes(locale)) continue;
5510
5732
  if (ingestFile(full, entry, "", locale, keys, warnings) && !locales.includes(locale)) {
5511
5733
  locales.push(locale);
@@ -5519,8 +5741,8 @@ var init_i18next_json2 = __esm({
5519
5741
  });
5520
5742
 
5521
5743
  // src/server/import/parsers/rails-yaml.ts
5522
- import { readdirSync as readdirSync12, readFileSync as readFileSync19 } from "fs";
5523
- import { join as join14 } from "path";
5744
+ import { readdirSync as readdirSync13, readFileSync as readFileSync20 } from "fs";
5745
+ import { join as join15 } from "path";
5524
5746
  function makeNode() {
5525
5747
  return /* @__PURE__ */ Object.create(null);
5526
5748
  }
@@ -5710,13 +5932,13 @@ function synthesizeIcu(forms, file, key, warnings) {
5710
5932
  }
5711
5933
  return `{count, plural, ${parts.join(" ")}}`;
5712
5934
  }
5713
- var LOCALE_RE8, CATEGORY_SET, railsYaml2;
5935
+ var LOCALE_RE9, CATEGORY_SET, railsYaml2;
5714
5936
  var init_rails_yaml2 = __esm({
5715
5937
  "src/server/import/parsers/rails-yaml.ts"() {
5716
5938
  "use strict";
5717
5939
  init_schema();
5718
5940
  init_placeholders2();
5719
- LOCALE_RE8 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/i;
5941
+ LOCALE_RE9 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/i;
5720
5942
  CATEGORY_SET = new Set(PLURAL_CATEGORIES);
5721
5943
  railsYaml2 = {
5722
5944
  name: "rails-yaml",
@@ -5740,18 +5962,18 @@ var init_rails_yaml2 = __esm({
5740
5962
  else flatten(v, key, locale, file);
5741
5963
  }
5742
5964
  };
5743
- for (const file of readdirSync12(localeRoot).sort()) {
5965
+ for (const file of readdirSync13(localeRoot).sort()) {
5744
5966
  if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
5745
5967
  let text;
5746
5968
  try {
5747
- text = readFileSync19(join14(localeRoot, file), "utf8");
5969
+ text = readFileSync20(join15(localeRoot, file), "utf8");
5748
5970
  } catch (e) {
5749
5971
  warnings.push(`rails-yaml: failed to read ${file}: ${e.message}`);
5750
5972
  continue;
5751
5973
  }
5752
5974
  const { roots } = parseYamlSubset(text, file, warnings);
5753
5975
  for (const token of Object.keys(roots).sort()) {
5754
- if (!LOCALE_RE8.test(token)) {
5976
+ if (!LOCALE_RE9.test(token)) {
5755
5977
  warnings.push(`rails-yaml: ${file}: top-level key "${token}" is not a locale; subtree skipped`);
5756
5978
  continue;
5757
5979
  }
@@ -5767,12 +5989,12 @@ var init_rails_yaml2 = __esm({
5767
5989
  });
5768
5990
 
5769
5991
  // src/server/import/parsers/apple-stringsdict.ts
5770
- import { readdirSync as readdirSync13, readFileSync as readFileSync20, statSync as statSync8 } from "fs";
5771
- import { join as join15 } from "path";
5992
+ import { readdirSync as readdirSync14, readFileSync as readFileSync21, statSync as statSync8 } from "fs";
5993
+ import { join as join16 } from "path";
5772
5994
  function localeFromLproj2(dir) {
5773
5995
  const m = dir.match(/^(.+)\.lproj$/);
5774
5996
  if (!m) return null;
5775
- return LOCALE_RE9.test(m[1]) ? m[1] : null;
5997
+ return LOCALE_RE10.test(m[1]) ? m[1] : null;
5776
5998
  }
5777
5999
  function decodeEntities2(s) {
5778
6000
  return s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d))).replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
@@ -5909,13 +6131,13 @@ function entryToIcu(key, entry, file, warnings) {
5909
6131
  if (forms.other === void 0) return warn(`variable "${arg}" has no "other" form; skipped`);
5910
6132
  return formsToIcu(arg, forms);
5911
6133
  }
5912
- var LOCALE_RE9, TABLE2, VAR_RE, appleStringsdict2;
6134
+ var LOCALE_RE10, TABLE2, VAR_RE, appleStringsdict2;
5913
6135
  var init_apple_stringsdict2 = __esm({
5914
6136
  "src/server/import/parsers/apple-stringsdict.ts"() {
5915
6137
  "use strict";
5916
6138
  init_schema();
5917
6139
  init_plurals();
5918
- LOCALE_RE9 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
6140
+ LOCALE_RE10 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
5919
6141
  TABLE2 = "Localizable.stringsdict";
5920
6142
  VAR_RE = /%#@([^@]*)@/g;
5921
6143
  appleStringsdict2 = {
@@ -5924,20 +6146,20 @@ var init_apple_stringsdict2 = __esm({
5924
6146
  const warnings = [];
5925
6147
  const keys = {};
5926
6148
  const locales = [];
5927
- for (const dir of readdirSync13(localeRoot).sort()) {
6149
+ for (const dir of readdirSync14(localeRoot).sort()) {
5928
6150
  const locale = localeFromLproj2(dir);
5929
6151
  if (!locale) continue;
5930
6152
  if (opts?.locales && !opts.locales.includes(locale)) continue;
5931
- const file = join15(localeRoot, dir, TABLE2);
6153
+ const file = join16(localeRoot, dir, TABLE2);
5932
6154
  let text;
5933
6155
  try {
5934
6156
  if (!statSync8(file).isFile()) continue;
5935
- text = readFileSync20(file, "utf8");
6157
+ text = readFileSync21(file, "utf8");
5936
6158
  } catch {
5937
6159
  continue;
5938
6160
  }
5939
6161
  locales.push(locale);
5940
- const others = readdirSync13(join15(localeRoot, dir)).filter(
6162
+ const others = readdirSync14(join16(localeRoot, dir)).filter(
5941
6163
  (f) => f.endsWith(".stringsdict") && f !== TABLE2
5942
6164
  );
5943
6165
  if (others.length) {
@@ -5975,6 +6197,7 @@ var init_parsers = __esm({
5975
6197
  "src/server/import/parsers/index.ts"() {
5976
6198
  "use strict";
5977
6199
  init_vue_i18n_json2();
6200
+ init_next_intl_json2();
5978
6201
  init_laravel_php2();
5979
6202
  init_flutter_arb2();
5980
6203
  init_apple_strings2();
@@ -5985,6 +6208,7 @@ var init_parsers = __esm({
5985
6208
  init_apple_stringsdict2();
5986
6209
  REGISTRY = {
5987
6210
  [vueI18nJson2.name]: vueI18nJson2,
6211
+ [nextIntlJson2.name]: nextIntlJson2,
5988
6212
  [laravelPhp2.name]: laravelPhp2,
5989
6213
  [flutterArb2.name]: flutterArb2,
5990
6214
  [appleStrings2.name]: appleStrings2,
@@ -6409,7 +6633,7 @@ var init_run2 = __esm({
6409
6633
  });
6410
6634
 
6411
6635
  // src/server/lint/outputs.ts
6412
- import { readFileSync as readFileSync21, existsSync as existsSync12 } from "fs";
6636
+ import { readFileSync as readFileSync22, existsSync as existsSync12 } from "fs";
6413
6637
  import { resolve as resolve8 } from "path";
6414
6638
  function checkOutputs(state, root) {
6415
6639
  const out = [];
@@ -6417,7 +6641,7 @@ function checkOutputs(state, root) {
6417
6641
  const result = getAdapter(output.adapter).export(state, output);
6418
6642
  for (const file of result.files) {
6419
6643
  const abs = resolve8(root, file.path);
6420
- const current = existsSync12(abs) ? readFileSync21(abs, "utf8") : null;
6644
+ const current = existsSync12(abs) ? readFileSync22(abs, "utf8") : null;
6421
6645
  if (current === null) {
6422
6646
  out.push({ ruleId: "output-stale", key: file.path, locale: "", severity: "error", message: "output file is missing; run `glotfile export`" });
6423
6647
  } else if (current !== file.contents) {
@@ -6541,6 +6765,9 @@ var init_assemble = __esm({
6541
6765
  OUTPUT_BY_FORMAT = {
6542
6766
  "laravel-php": { adapter: "laravel-php", path: "lang/{locale}/{namespace}.php" },
6543
6767
  "vue-i18n-json": { adapter: "vue-i18n-json", path: "src/locale/{locale}.json" },
6768
+ // rootRelative: write back to wherever the messages dir was found (messages/,
6769
+ // src/messages/, …) rather than assuming the conventional root-level location.
6770
+ "next-intl-json": { adapter: "next-intl-json", path: "{locale}.json", rootRelative: true },
6544
6771
  "flutter-arb": { adapter: "flutter-arb", path: "lib/l10n/app_{locale}.arb" },
6545
6772
  "apple-strings": { adapter: "apple-strings", path: "{locale}.lproj/Localizable.strings", rootRelative: true },
6546
6773
  // skipSourceLocale: ng extract-i18n owns messages.xlf (the source file); glotfile
@@ -6866,12 +7093,12 @@ var init_checks = __esm({
6866
7093
  });
6867
7094
 
6868
7095
  // src/server/ui-prefs.ts
6869
- import { readFileSync as readFileSync22 } from "fs";
7096
+ import { readFileSync as readFileSync23 } from "fs";
6870
7097
  import { homedir } from "os";
6871
- import { join as join16 } from "path";
7098
+ import { join as join17 } from "path";
6872
7099
  function readJson2(path) {
6873
7100
  try {
6874
- const parsed = JSON.parse(readFileSync22(path, "utf8"));
7101
+ const parsed = JSON.parse(readFileSync23(path, "utf8"));
6875
7102
  return parsed && typeof parsed === "object" ? parsed : {};
6876
7103
  } catch {
6877
7104
  return {};
@@ -6896,7 +7123,7 @@ var init_ui_prefs = __esm({
6896
7123
  THEMES = ["system", "light", "dark"];
6897
7124
  isThemeMode = (v) => THEMES.includes(v);
6898
7125
  isPanelWidth = (v) => typeof v === "number" && Number.isFinite(v) && v >= 120 && v <= 1200;
6899
- defaultUiPrefsPath = () => join16(homedir(), ".glotfile", "ui.json");
7126
+ defaultUiPrefsPath = () => join17(homedir(), ".glotfile", "ui.json");
6900
7127
  DEFAULTS = { theme: "system" };
6901
7128
  }
6902
7129
  });
@@ -6929,8 +7156,8 @@ var init_events = __esm({
6929
7156
  });
6930
7157
 
6931
7158
  // src/server/watch.ts
6932
- import { statSync as statSync9, readdirSync as readdirSync14 } from "fs";
6933
- import { join as join17 } from "path";
7159
+ import { statSync as statSync9, readdirSync as readdirSync15 } from "fs";
7160
+ import { join as join18 } from "path";
6934
7161
  import { createHash as createHash2 } from "crypto";
6935
7162
  function hashState(state) {
6936
7163
  return createHash2("sha1").update(serializeJson(state, state.config.format)).digest("hex");
@@ -6946,15 +7173,15 @@ function signature(statePath) {
6946
7173
  const parts = [];
6947
7174
  for (const rel of ["config.json", "keys.json"]) {
6948
7175
  try {
6949
- const s = statSync9(join17(dir, rel));
7176
+ const s = statSync9(join18(dir, rel));
6950
7177
  parts.push(`${rel}:${s.size}:${s.mtimeMs}`);
6951
7178
  } catch {
6952
7179
  }
6953
7180
  }
6954
7181
  try {
6955
- for (const name of readdirSync14(join17(dir, "locales")).sort()) {
7182
+ for (const name of readdirSync15(join18(dir, "locales")).sort()) {
6956
7183
  if (!name.endsWith(".json")) continue;
6957
- const s = statSync9(join17(dir, "locales", name));
7184
+ const s = statSync9(join18(dir, "locales", name));
6958
7185
  parts.push(`${name}:${s.size}:${s.mtimeMs}`);
6959
7186
  }
6960
7187
  } catch {
@@ -7033,13 +7260,13 @@ var init_watch = __esm({
7033
7260
  // src/server/api.ts
7034
7261
  import { Hono } from "hono";
7035
7262
  import { streamSSE } from "hono/streaming";
7036
- import { readFileSync as readFileSync23, existsSync as existsSync13, readdirSync as readdirSync15, statSync as statSync10, rmSync as rmSync6 } from "fs";
7263
+ import { readFileSync as readFileSync24, existsSync as existsSync13, readdirSync as readdirSync16, statSync as statSync10, rmSync as rmSync6 } from "fs";
7037
7264
  import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
7038
7265
  function projectName(root) {
7039
7266
  const nameFile = resolve9(root, ".idea", ".name");
7040
7267
  if (existsSync13(nameFile)) {
7041
7268
  try {
7042
- const name = readFileSync23(nameFile, "utf8").trim();
7269
+ const name = readFileSync24(nameFile, "utf8").trim();
7043
7270
  if (name) return name;
7044
7271
  } catch {
7045
7272
  }
@@ -7205,7 +7432,7 @@ function createApi(deps) {
7205
7432
  if (depth > 4) return;
7206
7433
  let entries = [];
7207
7434
  try {
7208
- entries = readdirSync15(dir);
7435
+ entries = readdirSync16(dir);
7209
7436
  } catch {
7210
7437
  return;
7211
7438
  }
@@ -8270,7 +8497,7 @@ __export(server_exports, {
8270
8497
  import { Hono as Hono2 } from "hono";
8271
8498
  import { serve } from "@hono/node-server";
8272
8499
  import { fileURLToPath } from "url";
8273
- import { dirname as dirname4, join as join18, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
8500
+ import { dirname as dirname4, join as join19, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
8274
8501
  import { readFile, stat } from "fs/promises";
8275
8502
  import { createServer } from "net";
8276
8503
  import open from "open";
@@ -8326,7 +8553,7 @@ function buildApp(opts) {
8326
8553
  const file = await readFileResponse(target);
8327
8554
  if (file) return file;
8328
8555
  }
8329
- const index = await readFileResponse(join18(root, "index.html"));
8556
+ const index = await readFileResponse(join19(root, "index.html"));
8330
8557
  if (index) return index;
8331
8558
  return c.notFound();
8332
8559
  });
@@ -8396,7 +8623,7 @@ var init_server = __esm({
8396
8623
  init_scanner();
8397
8624
  init_usage();
8398
8625
  here = dirname4(fileURLToPath(import.meta.url));
8399
- DEFAULT_UI_DIR = join18(here, "..", "ui");
8626
+ DEFAULT_UI_DIR = join19(here, "..", "ui");
8400
8627
  MIME = {
8401
8628
  ".html": "text/html; charset=utf-8",
8402
8629
  ".js": "text/javascript; charset=utf-8",
@@ -8428,8 +8655,8 @@ var init_server = __esm({
8428
8655
  // src/server/cli.ts
8429
8656
  init_state();
8430
8657
  init_stats();
8431
- import { resolve as resolve11, dirname as dirname5, join as join19, basename as basename2 } from "path";
8432
- import { readFileSync as readFileSync24, existsSync as existsSync14, mkdirSync as mkdirSync6, cpSync } from "fs";
8658
+ import { resolve as resolve11, dirname as dirname5, join as join20, basename as basename2 } from "path";
8659
+ import { readFileSync as readFileSync25, existsSync as existsSync14, mkdirSync as mkdirSync6, cpSync } from "fs";
8433
8660
  import { fileURLToPath as fileURLToPath2 } from "url";
8434
8661
 
8435
8662
  // src/server/agent-cli.ts
@@ -8815,7 +9042,7 @@ function translateSelection(args) {
8815
9042
  }
8816
9043
  function readStdin() {
8817
9044
  try {
8818
- return readFileSync24(0, "utf8");
9045
+ return readFileSync25(0, "utf8");
8819
9046
  } catch {
8820
9047
  return "";
8821
9048
  }
@@ -9083,15 +9310,15 @@ async function runContextBatchAction(args, pending, action, projectRoot) {
9083
9310
  function sarifContextFor(statePath) {
9084
9311
  if (detectFormat(statePath) === "split") {
9085
9312
  const dir = splitDirFor(statePath);
9086
- const keysPath = join19(dir, "keys.json");
9313
+ const keysPath = join20(dir, "keys.json");
9087
9314
  return {
9088
9315
  keysUri: `${basename2(dir)}/keys.json`,
9089
- keysRawText: existsSync14(keysPath) ? readFileSync24(keysPath, "utf8") : ""
9316
+ keysRawText: existsSync14(keysPath) ? readFileSync25(keysPath, "utf8") : ""
9090
9317
  };
9091
9318
  }
9092
9319
  return {
9093
9320
  keysUri: basename2(statePath),
9094
- keysRawText: existsSync14(statePath) ? readFileSync24(statePath, "utf8") : ""
9321
+ keysRawText: existsSync14(statePath) ? readFileSync25(statePath, "utf8") : ""
9095
9322
  };
9096
9323
  }
9097
9324
  function printReport(report, format, statePath) {
@@ -9392,10 +9619,10 @@ function runSplit(args) {
9392
9619
  `Split catalog into ${splitDirFor(args.statePath)}/ (config.json, keys.json, locales/ \u2014 up to ${state.config.locales.length} locale files). Removed ${args.statePath}.`
9393
9620
  );
9394
9621
  }
9395
- var SKILL_SRC = join19(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "skill");
9622
+ var SKILL_SRC = join20(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "skill");
9396
9623
  function runSkill(args) {
9397
9624
  if (args.print) {
9398
- console.log(readFileSync24(join19(SKILL_SRC, "SKILL.md"), "utf8").trimEnd());
9625
+ console.log(readFileSync25(join20(SKILL_SRC, "SKILL.md"), "utf8").trimEnd());
9399
9626
  return;
9400
9627
  }
9401
9628
  const dest = resolve11(process.cwd(), ".claude", "skills", "glotfile");
@@ -9785,8 +10012,8 @@ ${formatOpts([...options, ...GLOBAL_OPTS])}`);
9785
10012
  );
9786
10013
  }
9787
10014
  function printVersion() {
9788
- const pkgPath = join19(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
9789
- console.log(JSON.parse(readFileSync24(pkgPath, "utf8")).version);
10015
+ const pkgPath = join20(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
10016
+ console.log(JSON.parse(readFileSync25(pkgPath, "utf8")).version);
9790
10017
  }
9791
10018
  async function main(argv) {
9792
10019
  const args = parseArgs(argv);