@gabrielbourget/i18n-foundation 0.1.0 → 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1 - 2026-06-15
4
+
5
+ - Added namespace source-root resolver helpers for mixed app-owned and foundation-owned locale roots.
6
+ - Extended generated asset helpers and source verification to accept namespace source-root resolvers while preserving the existing single-root API.
7
+ - Added package publish guardrails with `prepack`, `prepublishOnly`, `verify`, and test typechecking.
8
+ - Added package CI and a Node version pin.
9
+ - Corrected README examples to use the public subpath imports.
10
+
11
+ ## 0.1.0 - 2026-06-14
12
+
13
+ - Initial private i18n foundation package release.
14
+ - Extracted shared i18next runtime helpers, env parsing, manifest helpers, pseudolocalization, asset helpers, verifier helpers, and foundation-owned common locale JSON.
package/README.md CHANGED
@@ -93,12 +93,9 @@ namespace.
93
93
  The app facade owns all app-specific registry values:
94
94
 
95
95
  ```ts
96
- import {
97
- buildClientRuntimeConfig,
98
- createI18nClientEnvReader,
99
- createI18nProvider,
100
- validateDevManifest,
101
- } from "@gabrielbourget/i18n-foundation"
96
+ import { createI18nProvider } from "@gabrielbourget/i18n-foundation/core/client"
97
+ import { buildClientRuntimeConfig, createI18nClientEnvReader } from "@gabrielbourget/i18n-foundation/env/client"
98
+ import { validateDevManifest } from "@gabrielbourget/i18n-foundation/manifest"
102
99
 
103
100
  const registry = {
104
101
  defaultLocale: DEFAULT_LOCALE,
@@ -131,7 +128,15 @@ Facade scripts should pass explicit app paths and registries into foundation
131
128
  helpers:
132
129
 
133
130
  ```ts
134
- import { buildI18nManifest, generateI18nAssets, verifyI18nSource } from "@gabrielbourget/i18n-foundation"
131
+ import { FOUNDATION_COMMON_NAMESPACES } from "@gabrielbourget/i18n-foundation/common"
132
+ import { createI18nNamespaceSourceRootResolver, generateI18nAssets } from "@gabrielbourget/i18n-foundation/assets"
133
+ import { buildI18nManifest } from "@gabrielbourget/i18n-foundation/manifest"
134
+ import { verifyI18nSource } from "@gabrielbourget/i18n-foundation/verifier"
135
+
136
+ const resolveNamespaceSourceRoot = createI18nNamespaceSourceRootResolver({
137
+ defaultLocalesRoot: appLocalesRoot,
138
+ overrides: [{ localesRoot: foundationLocalesRoot, namespaces: FOUNDATION_COMMON_NAMESPACES }],
139
+ })
135
140
 
136
141
  const manifest = buildI18nManifest({
137
142
  defaultLocale: DEFAULT_LOCALE,
@@ -141,13 +146,14 @@ const manifest = buildI18nManifest({
141
146
  })
142
147
 
143
148
  generateI18nAssets({
144
- localesRoot,
145
149
  outputRoot,
146
150
  manifest,
151
+ resolveNamespaceSourceRoot,
147
152
  })
148
153
 
149
154
  await verifyI18nSource({
150
- localesRoot,
155
+ localesRoot: appLocalesRoot,
156
+ resolveNamespaceSourceRoot,
151
157
  availableNamespaces: AVAILABLE_NAMESPACES,
152
158
  declaredLocales: DECLARED_LOCALES,
153
159
  defaultLocale: DEFAULT_LOCALE,
@@ -5,8 +5,24 @@ export type TGeneratedI18nAsset<Locale extends string = string, Namespace extend
5
5
  sourcePath: string;
6
6
  outputPath: string;
7
7
  };
8
- export type TGenerateI18nAssetsArgs<Locale extends string, Namespace extends string> = {
8
+ export type TI18nNamespaceSourceRootResolver<Namespace extends string = string> = (namespace: Namespace) => string;
9
+ export type TI18nNamespaceSourceRootOverride<Namespace extends string = string> = {
9
10
  localesRoot: string;
11
+ namespaces: readonly Namespace[];
12
+ };
13
+ export type TCreateI18nNamespaceSourceRootResolverArgs<Namespace extends string = string> = {
14
+ defaultLocalesRoot: string;
15
+ overrides?: readonly TI18nNamespaceSourceRootOverride<Namespace>[] | undefined;
16
+ };
17
+ export type TResolveI18nNamespaceSourcePathArgs<Locale extends string = string, Namespace extends string = string> = {
18
+ locale: Locale;
19
+ namespace: Namespace;
20
+ localesRoot?: string | undefined;
21
+ resolveNamespaceSourceRoot?: TI18nNamespaceSourceRootResolver<Namespace> | undefined;
22
+ };
23
+ export type TGenerateI18nAssetsArgs<Locale extends string, Namespace extends string> = {
24
+ localesRoot?: string | undefined;
25
+ resolveNamespaceSourceRoot?: TI18nNamespaceSourceRootResolver<Namespace> | undefined;
10
26
  outputRoot: string;
11
27
  manifest: TI18nManifest<Locale, Namespace>;
12
28
  cleanOutputRoot?: boolean | undefined;
@@ -17,7 +33,8 @@ export type TGenerateI18nAssetsResult<Locale extends string, Namespace extends s
17
33
  assets: readonly TGeneratedI18nAsset<Locale, Namespace>[];
18
34
  };
19
35
  export type TValidateGeneratedI18nAssetsArgs<Locale extends string, Namespace extends string> = {
20
- localesRoot: string;
36
+ localesRoot?: string | undefined;
37
+ resolveNamespaceSourceRoot?: TI18nNamespaceSourceRootResolver<Namespace> | undefined;
21
38
  generatedRoot: string;
22
39
  manifest: TI18nManifest<Locale, Namespace>;
23
40
  };
@@ -30,6 +47,8 @@ export declare const listJSONFileNames: (directoryPath: string) => string[];
30
47
  export declare const assertEqualArrays: (label: string, expected: readonly string[], actual: readonly string[]) => void;
31
48
  export declare const ensureDirectory: (directoryPath: string) => void;
32
49
  export declare const removeDirectoryIfExists: (directoryPath: string) => void;
33
- export declare const generateI18nAssets: <Locale extends string, Namespace extends string>({ localesRoot, outputRoot, manifest, cleanOutputRoot, }: TGenerateI18nAssetsArgs<Locale, Namespace>) => TGenerateI18nAssetsResult<Locale, Namespace>;
34
- export declare const validateGeneratedI18nAssets: <Locale extends string, Namespace extends string>({ localesRoot, generatedRoot, manifest, }: TValidateGeneratedI18nAssetsArgs<Locale, Namespace>) => void;
50
+ export declare const createI18nNamespaceSourceRootResolver: <Namespace extends string>({ defaultLocalesRoot, overrides, }: TCreateI18nNamespaceSourceRootResolverArgs<Namespace>) => TI18nNamespaceSourceRootResolver<Namespace>;
51
+ export declare const resolveI18nNamespaceSourcePath: <Locale extends string, Namespace extends string>({ locale, namespace, localesRoot, resolveNamespaceSourceRoot, }: TResolveI18nNamespaceSourcePathArgs<Locale, Namespace>) => string;
52
+ export declare const generateI18nAssets: <Locale extends string, Namespace extends string>({ localesRoot, resolveNamespaceSourceRoot, outputRoot, manifest, cleanOutputRoot, }: TGenerateI18nAssetsArgs<Locale, Namespace>) => TGenerateI18nAssetsResult<Locale, Namespace>;
53
+ export declare const validateGeneratedI18nAssets: <Locale extends string, Namespace extends string>({ localesRoot, resolveNamespaceSourceRoot, generatedRoot, manifest, }: TValidateGeneratedI18nAssetsArgs<Locale, Namespace>) => void;
35
54
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/assets/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzD,MAAM,MAAM,mBAAmB,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,EAAE,SAAS,SAAS,MAAM,GAAG,MAAM,IAAI;IACnG,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,SAAS,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,uBAAuB,CAAC,MAAM,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,IAAI;IACrF,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAC1C,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CACtC,CAAA;AAED,MAAM,MAAM,yBAAyB,CAAC,MAAM,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,IAAI;IACvF,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,SAAS,mBAAmB,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CAAA;CAC1D,CAAA;AAED,MAAM,MAAM,gCAAgC,CAAC,MAAM,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,IAAI;IAC9F,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;CAC3C,CAAA;AAED,eAAO,MAAM,QAAQ,GAAI,UAAU,MAAM,KAAG,MAA2C,CAAA;AAEvF,eAAO,MAAM,SAAS,GAAI,UAAU,MAAM,EAAE,OAAO,MAAM,KAAG,IAE3D,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,UAAU,MAAM,KAAG,OAQ/C,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,EAAE,OAAO,OAAO,KAAG,IAEhE,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,eAAe,MAAM,KAAG,MAAM,EAMhE,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,eAAe,MAAM,KAAG,MAAM,EAM/D,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,EAAE,UAAU,SAAS,MAAM,EAAE,EAAE,QAAQ,SAAS,MAAM,EAAE,KAAG,IAOzG,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,eAAe,MAAM,KAAG,IAEvD,CAAA;AAED,eAAO,MAAM,uBAAuB,GAAI,eAAe,MAAM,KAAG,IAE/D,CAAA;AAWD,eAAO,MAAM,kBAAkB,GAAI,MAAM,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,EAAE,yDAKjF,uBAAuB,CAAC,MAAM,EAAE,SAAS,CAAC,KAAG,yBAAyB,CAAC,MAAM,EAAE,SAAS,CAgC1F,CAAA;AAED,eAAO,MAAM,2BAA2B,GAAI,MAAM,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,EAAE,2CAI1F,gCAAgC,CAAC,MAAM,EAAE,SAAS,CAAC,KAAG,IA0CxD,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/assets/index.ts"],"names":[],"mappings":"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"}
@@ -43,6 +43,25 @@ export const ensureDirectory = (directoryPath) => {
43
43
  export const removeDirectoryIfExists = (directoryPath) => {
44
44
  fs.rmSync(directoryPath, { recursive: true, force: true });
45
45
  };
46
+ export const createI18nNamespaceSourceRootResolver = ({ defaultLocalesRoot, overrides = [], }) => {
47
+ const overrideRootByNamespace = new Map();
48
+ for (const override of overrides) {
49
+ for (const namespace of override.namespaces) {
50
+ if (overrideRootByNamespace.has(namespace)) {
51
+ throw new Error(`[i18n assets] Namespace source root configured more than once: ${namespace}`);
52
+ }
53
+ overrideRootByNamespace.set(namespace, override.localesRoot);
54
+ }
55
+ }
56
+ return (namespace) => overrideRootByNamespace.get(namespace) ?? defaultLocalesRoot;
57
+ };
58
+ export const resolveI18nNamespaceSourcePath = ({ locale, namespace, localesRoot, resolveNamespaceSourceRoot, }) => {
59
+ const resolvedLocalesRoot = resolveNamespaceSourceRoot?.(namespace) ?? localesRoot;
60
+ if (!resolvedLocalesRoot) {
61
+ throw new Error("[i18n assets] Either localesRoot or resolveNamespaceSourceRoot must be provided.");
62
+ }
63
+ return path.join(resolvedLocalesRoot, locale, `${namespace}.json`);
64
+ };
46
65
  const assertFileContentMatches = (label, expectedPath, actualPath) => {
47
66
  const expected = readUTF8(expectedPath);
48
67
  const actual = readUTF8(actualPath);
@@ -50,7 +69,7 @@ const assertFileContentMatches = (label, expectedPath, actualPath) => {
50
69
  throw new Error(`${label} does not match source file.\nsource: ${expectedPath}\nasset: ${actualPath}`);
51
70
  }
52
71
  };
53
- export const generateI18nAssets = ({ localesRoot, outputRoot, manifest, cleanOutputRoot = true, }) => {
72
+ export const generateI18nAssets = ({ localesRoot, resolveNamespaceSourceRoot, outputRoot, manifest, cleanOutputRoot = true, }) => {
54
73
  if (cleanOutputRoot) {
55
74
  removeDirectoryIfExists(outputRoot);
56
75
  }
@@ -60,7 +79,12 @@ export const generateI18nAssets = ({ localesRoot, outputRoot, manifest, cleanOut
60
79
  const localeOutputPath = path.join(outputRoot, locale);
61
80
  ensureDirectory(localeOutputPath);
62
81
  for (const namespace of manifest.namespaces) {
63
- const sourcePath = path.join(localesRoot, locale, `${namespace}.json`);
82
+ const sourcePath = resolveI18nNamespaceSourcePath({
83
+ locale,
84
+ localesRoot,
85
+ namespace,
86
+ resolveNamespaceSourceRoot,
87
+ });
64
88
  const outputPath = path.join(localeOutputPath, `${namespace}.json`);
65
89
  readJSONFile(sourcePath);
66
90
  writeUTF8(outputPath, readUTF8(sourcePath));
@@ -75,7 +99,7 @@ export const generateI18nAssets = ({ localesRoot, outputRoot, manifest, cleanOut
75
99
  outputRoot,
76
100
  };
77
101
  };
78
- export const validateGeneratedI18nAssets = ({ localesRoot, generatedRoot, manifest, }) => {
102
+ export const validateGeneratedI18nAssets = ({ localesRoot, resolveNamespaceSourceRoot, generatedRoot, manifest, }) => {
79
103
  const manifestPath = path.join(generatedRoot, "manifest.json");
80
104
  if (!fs.existsSync(generatedRoot))
81
105
  throw new Error(`[i18n assets] Generated root not found: ${generatedRoot}`);
@@ -96,7 +120,12 @@ export const validateGeneratedI18nAssets = ({ localesRoot, generatedRoot, manife
96
120
  const expectedFileNames = manifest.namespaces.map((namespace) => `${namespace}.json`);
97
121
  assertEqualArrays(`[i18n assets] generated namespace files for ${locale}`, expectedFileNames, listJSONFileNames(generatedLocalePath));
98
122
  for (const namespace of manifest.namespaces) {
99
- assertFileContentMatches(`[i18n assets] ${locale}/${namespace}.json`, path.join(localesRoot, locale, `${namespace}.json`), path.join(generatedLocalePath, `${namespace}.json`));
123
+ assertFileContentMatches(`[i18n assets] ${locale}/${namespace}.json`, resolveI18nNamespaceSourcePath({
124
+ locale,
125
+ localesRoot,
126
+ namespace,
127
+ resolveNamespaceSourceRoot,
128
+ }), path.join(generatedLocalePath, `${namespace}.json`));
100
129
  }
101
130
  }
102
131
  };
@@ -1,6 +1,8 @@
1
+ import { type TI18nNamespaceSourceRootResolver } from "../assets/index.js";
1
2
  import { type TDomainSeedSourceContract, type TDomainTranslationContract } from "./domainContracts.js";
2
3
  export type TVerifyI18nSourceArgs<Locale extends string = string, Namespace extends string = string> = {
3
4
  localesRoot: string;
5
+ resolveNamespaceSourceRoot?: TI18nNamespaceSourceRootResolver<Namespace> | undefined;
4
6
  availableNamespaces: readonly Namespace[];
5
7
  declaredLocales?: readonly Locale[] | undefined;
6
8
  defaultLocale: Locale;
@@ -21,5 +23,5 @@ export type TVerifyI18nSourceResult = {
21
23
  domainSeedSourceContractCount: number;
22
24
  domainSeedSourceCodeCount: number;
23
25
  };
24
- export declare const verifyI18nSource: <Locale extends string, Namespace extends string>({ localesRoot, availableNamespaces, declaredLocales, defaultLocale, initialLoadNamespaces, repoRoot, domainTranslationContracts, domainSeedSourceContracts, structuredNamespaceKeyConventionExceptions, }: TVerifyI18nSourceArgs<Locale, Namespace>) => Promise<TVerifyI18nSourceResult>;
26
+ export declare const verifyI18nSource: <Locale extends string, Namespace extends string>({ localesRoot, availableNamespaces, declaredLocales, defaultLocale, initialLoadNamespaces, repoRoot, domainTranslationContracts, domainSeedSourceContracts, structuredNamespaceKeyConventionExceptions, resolveNamespaceSourceRoot, }: TVerifyI18nSourceArgs<Locale, Namespace>) => Promise<TVerifyI18nSourceResult>;
25
27
  //# sourceMappingURL=verifyI18nSource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"verifyI18nSource.d.ts","sourceRoot":"","sources":["../../src/verifier/verifyI18nSource.ts"],"names":[],"mappings":"AAWA,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,EAChC,MAAM,sBAAsB,CAAA;AAE7B,MAAM,MAAM,qBAAqB,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,EAAE,SAAS,SAAS,MAAM,GAAG,MAAM,IAAI;IACrG,WAAW,EAAE,MAAM,CAAA;IACnB,mBAAmB,EAAE,SAAS,SAAS,EAAE,CAAA;IACzC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IAC/C,aAAa,EAAE,MAAM,CAAA;IACrB,qBAAqB,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,CAAA;IACxD,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,0BAA0B,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAA;IAC9E,yBAAyB,CAAC,EAAE,SAAS,yBAAyB,EAAE,GAAG,SAAS,CAAA;IAC5E,0CAA0C,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,CAAA;CAC9E,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,wBAAwB,EAAE,MAAM,CAAA;IAChC,YAAY,EAAE,MAAM,CAAA;IACpB,8BAA8B,EAAE,MAAM,CAAA;IACtC,gCAAgC,EAAE,MAAM,CAAA;IACxC,6BAA6B,EAAE,MAAM,CAAA;IACrC,yBAAyB,EAAE,MAAM,CAAA;CAClC,CAAA;AAUD,eAAO,MAAM,gBAAgB,GAAU,MAAM,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,EAAE,2MAUrF,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,KAAG,OAAO,CAAC,uBAAuB,CAsO5E,CAAA"}
1
+ {"version":3,"file":"verifyI18nSource.d.ts","sourceRoot":"","sources":["../../src/verifier/verifyI18nSource.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkC,KAAK,gCAAgC,EAAE,MAAM,oBAAoB,CAAA;AAW1G,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,EAChC,MAAM,sBAAsB,CAAA;AAE7B,MAAM,MAAM,qBAAqB,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,EAAE,SAAS,SAAS,MAAM,GAAG,MAAM,IAAI;IACrG,WAAW,EAAE,MAAM,CAAA;IACnB,0BAA0B,CAAC,EAAE,gCAAgC,CAAC,SAAS,CAAC,GAAG,SAAS,CAAA;IACpF,mBAAmB,EAAE,SAAS,SAAS,EAAE,CAAA;IACzC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IAC/C,aAAa,EAAE,MAAM,CAAA;IACrB,qBAAqB,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,CAAA;IACxD,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,0BAA0B,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAA;IAC9E,yBAAyB,CAAC,EAAE,SAAS,yBAAyB,EAAE,GAAG,SAAS,CAAA;IAC5E,0CAA0C,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,CAAA;CAC9E,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,wBAAwB,EAAE,MAAM,CAAA;IAChC,YAAY,EAAE,MAAM,CAAA;IACpB,8BAA8B,EAAE,MAAM,CAAA;IACtC,gCAAgC,EAAE,MAAM,CAAA;IACxC,6BAA6B,EAAE,MAAM,CAAA;IACrC,yBAAyB,EAAE,MAAM,CAAA;CAClC,CAAA;AAUD,eAAO,MAAM,gBAAgB,GAAU,MAAM,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,EAAE,uOAWrF,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,KAAG,OAAO,CAAC,uBAAuB,CA2O5E,CAAA"}
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { resolveI18nNamespaceSourcePath } from "../assets/index.js";
2
3
  import { getDeclaredInterpolationTokens, getTranslationStringEntries, parseICUContract, pushContractDiffs, } from "./icuContracts.js";
3
4
  import { getLeafShapes, normalizeShapeEntries, validateKeyConventions } from "./keyShape.js";
4
5
  import { listDirNames, listJSONFiles, readJSONFileOrThrow } from "./fileSystem.js";
@@ -10,7 +11,7 @@ const assertNoItems = (label, items) => {
10
11
  return;
11
12
  throw new Error(`[i18n verify] ${label}\n${formatList(items)}`);
12
13
  };
13
- export const verifyI18nSource = async ({ localesRoot, availableNamespaces, declaredLocales, defaultLocale, initialLoadNamespaces = [], repoRoot, domainTranslationContracts = [], domainSeedSourceContracts = [], structuredNamespaceKeyConventionExceptions = [], }) => {
14
+ export const verifyI18nSource = async ({ localesRoot, availableNamespaces, declaredLocales, defaultLocale, initialLoadNamespaces = [], repoRoot, domainTranslationContracts = [], domainSeedSourceContracts = [], structuredNamespaceKeyConventionExceptions = [], resolveNamespaceSourceRoot, }) => {
14
15
  if (availableNamespaces.length === 0) {
15
16
  throw new Error("[i18n verify] availableNamespaces must contain at least one namespace");
16
17
  }
@@ -34,21 +35,26 @@ export const verifyI18nSource = async ({ localesRoot, availableNamespaces, decla
34
35
  const namespaceUnion = new Set();
35
36
  for (const locale of locales) {
36
37
  const localeDir = path.join(localesRoot, locale);
37
- const files = await listJSONFiles(localeDir);
38
- const namespaces = files.map((file) => file.replace(/\.json$/, ""));
39
- if (files.length === 0) {
38
+ const namespaces = resolveNamespaceSourceRoot
39
+ ? [...availableNamespaces]
40
+ : (await listJSONFiles(localeDir)).map((file) => file.replace(/\.json$/, ""));
41
+ if (namespaces.length === 0) {
40
42
  throw new Error(`[i18n verify] No translation sheets found for locale=${locale}`);
41
43
  }
42
44
  namespacesByLocale.set(locale, namespaces);
43
45
  sheetsByLocale.set(locale, new Map());
44
46
  namespaces.forEach((namespace) => namespaceUnion.add(namespace));
45
- for (const file of files) {
46
- const namespace = file.replace(/\.json$/, "");
47
- const filePath = path.join(localeDir, file);
47
+ for (const namespace of namespaces) {
48
+ const filePath = resolveI18nNamespaceSourcePath({
49
+ locale,
50
+ localesRoot,
51
+ namespace,
52
+ resolveNamespaceSourceRoot,
53
+ });
48
54
  const { json: sheet, raw } = await readJSONFileOrThrow(filePath, "[i18n verify]");
49
55
  const canonicalSheet = formatI18nSheet(sheet);
50
56
  if (raw !== canonicalSheet) {
51
- sheetOrderingErrors.push(`${locale}/${file}`);
57
+ sheetOrderingErrors.push(`${locale}/${namespace}.json`);
52
58
  }
53
59
  sheetsByLocale.get(locale)?.set(namespace, sheet);
54
60
  sheetCount += 1;
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "@gabrielbourget/i18n-foundation",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Shared i18n runtime foundation for Wavemap and Waveguide facade packages.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
+ "packageManager": "pnpm@10.18.3",
9
10
  "files": [
10
11
  "dist",
11
12
  "locales",
12
- "README.md"
13
+ "README.md",
14
+ "CHANGELOG.md"
13
15
  ],
14
16
  "sideEffects": false,
15
17
  "publishConfig": {
@@ -68,6 +70,18 @@
68
70
  "import": "./dist/verifier/index.js"
69
71
  }
70
72
  },
73
+ "scripts": {
74
+ "build": "tsc -p tsconfig.json",
75
+ "format": "prettier --write .",
76
+ "format:check": "prettier --check .",
77
+ "prepack": "pnpm build",
78
+ "prepublishOnly": "pnpm verify",
79
+ "test": "vitest run",
80
+ "test:unit": "vitest run",
81
+ "typecheck": "tsc --noEmit -p tsconfig.json",
82
+ "typecheck:tests": "tsc --noEmit -p tsconfig.vitest.json",
83
+ "verify": "pnpm format:check && pnpm typecheck && pnpm typecheck:tests && pnpm test"
84
+ },
71
85
  "peerDependencies": {
72
86
  "i18next": "^23.16.8",
73
87
  "i18next-icu": "^2.4.1",
@@ -91,13 +105,5 @@
91
105
  "react-i18next": "13.5.0",
92
106
  "typescript": "5.9.3",
93
107
  "vitest": "4.0.18"
94
- },
95
- "scripts": {
96
- "build": "tsc -p tsconfig.json",
97
- "format": "prettier --write .",
98
- "format:check": "prettier --check .",
99
- "test": "vitest run",
100
- "test:unit": "vitest run",
101
- "typecheck": "tsc --noEmit -p tsconfig.json"
102
108
  }
103
- }
109
+ }