@ankhorage/orchestrator-module-expo-localization 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,13 @@
1
+ # @ankhorage/orchestrator-module-expo-localization
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Align the module repo with the agreed standard by shipping module-owned template files, adding knip verification, and tightening CI checks.
8
+
9
+ ## 0.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Add the first publishable expo-localization module for `@ankhorage/orchestrator`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ankhorage
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @ankhorage/orchestrator-module-expo-localization
2
+
3
+ `@ankhorage/orchestrator-module-expo-localization` is the first standalone module extracted for `@ankhorage/orchestrator`. It replaces the old localization plugin contract from `ankhorage4/packages/plugin-kit` with a pure module that only returns supported orchestrator actions.
4
+
5
+ The repo follows the module-owned template pattern: generated runtime files come from checked-in [`templates/*.tpl`](./templates) assets that the module reads and materializes into orchestrator `write-files` actions.
6
+
7
+ ## What it does
8
+
9
+ - ensures `i18next`, `react-i18next`, and `expo-localization`
10
+ - writes localization runtime files into `src/plugins/localization`
11
+ - patches `src/app/_layout.tsx` to wrap the generated app shell with `LocalizationPluginProvider`
12
+ - patches `app.config.ts` to register the Expo localization config plugin
13
+ - keeps `ankh.config.json` aligned at `settings.localization`
14
+
15
+ The module does not implement or depend on executor behavior outside the orchestrator contract. It only emits `ensure-packages`, `write-files`, `patch-text-block`, and `json-set`.
16
+
17
+ ## Install
18
+
19
+ ```sh
20
+ bun add @ankhorage/orchestrator @ankhorage/orchestrator-module-expo-localization
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```ts
26
+ import { createOrchestrator } from '@ankhorage/orchestrator';
27
+ import { expoLocalizationModule } from '@ankhorage/orchestrator-module-expo-localization';
28
+
29
+ const orchestrator = createOrchestrator({
30
+ modules: [expoLocalizationModule],
31
+ projectRoot: '/absolute/path/to/project',
32
+ });
33
+
34
+ await orchestrator.installModule('expo-localization', {
35
+ config: {
36
+ defaultLocale: 'en',
37
+ locales: ['en', 'de'],
38
+ translations: {
39
+ en: { hello: 'Hello' },
40
+ de: { hello: 'Hallo' },
41
+ },
42
+ },
43
+ });
44
+ ```
45
+
46
+ ## Config
47
+
48
+ ```ts
49
+ {
50
+ defaultLocale?: string;
51
+ locales?: string[];
52
+ translations?: Record<string, Record<string, string>>;
53
+ }
54
+ ```
55
+
56
+ Defaults:
57
+
58
+ - `defaultLocale`: `"en"`
59
+ - `locales`: `["en"]`
60
+ - `translations`: `{}`
61
+
62
+ ## Assumptions
63
+
64
+ - The target app already has a generated `src/app/_layout.tsx`.
65
+ - The target app uses the standard `@/` alias layout generated by the surrounding Ankhorage toolchain.
66
+
67
+ This package is meant for the newer `@ankhorage/orchestrator` stack and supersedes the old `@ankh/plugin-kit` localization plugin path that is planned for removal.
68
+
69
+ ## Development
70
+
71
+ ```sh
72
+ bun install
73
+ bun run format:check
74
+ bun run lint
75
+ bun run test
76
+ bun run build
77
+ bun run knip
78
+ ```
@@ -0,0 +1,7 @@
1
+ export interface ExpoLocalizationModuleConfig {
2
+ defaultLocale?: string;
3
+ locales?: string[];
4
+ translations?: Record<string, Record<string, string>>;
5
+ }
6
+ export declare function parseExpoLocalizationModuleConfig(input: unknown): Required<ExpoLocalizationModuleConfig>;
7
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,4BAA4B;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACvD;AAED,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,OAAO,GACb,QAAQ,CAAC,4BAA4B,CAAC,CAmCxC"}
package/dist/config.js ADDED
@@ -0,0 +1,29 @@
1
+ export function parseExpoLocalizationModuleConfig(input) {
2
+ const fallback = {
3
+ defaultLocale: 'en',
4
+ locales: ['en'],
5
+ translations: {},
6
+ };
7
+ if (!isRecord(input)) {
8
+ return fallback;
9
+ }
10
+ const defaultLocale = typeof input.defaultLocale === 'string' && input.defaultLocale.trim().length > 0
11
+ ? input.defaultLocale.trim()
12
+ : fallback.defaultLocale;
13
+ const locales = Array.isArray(input.locales) && input.locales.every((value) => typeof value === 'string')
14
+ ? Array.from(new Set(input.locales.map((value) => value.trim()).filter(Boolean)))
15
+ : fallback.locales;
16
+ const translations = isRecord(input.translations) &&
17
+ Object.values(input.translations).every((value) => isRecord(value) && Object.values(value).every((entry) => typeof entry === 'string'))
18
+ ? input.translations
19
+ : fallback.translations;
20
+ return {
21
+ defaultLocale,
22
+ locales: locales.length > 0 ? locales : fallback.locales,
23
+ translations,
24
+ };
25
+ }
26
+ function isRecord(value) {
27
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
28
+ }
29
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,iCAAiC,CAC/C,KAAc;IAEd,MAAM,QAAQ,GAA2C;QACvD,aAAa,EAAE,IAAI;QACnB,OAAO,EAAE,CAAC,IAAI,CAAC;QACf,YAAY,EAAE,EAAE;KACjB,CAAC;IAEF,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,aAAa,GACjB,OAAO,KAAK,CAAC,aAAa,KAAK,QAAQ,IAAI,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;QAC9E,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE;QAC5B,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC;IAE7B,MAAM,OAAO,GACX,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC;QACvF,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QACjF,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;IAEvB,MAAM,YAAY,GAChB,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC;QAC5B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,KAAK,CACrC,CAAC,KAAK,EAAE,EAAE,CACR,QAAQ,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CACtF;QACC,CAAC,CAAE,KAAK,CAAC,YAAuD;QAChE,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;IAE5B,OAAO;QACL,aAAa;QACb,OAAO,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO;QACxD,YAAY;KACb,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { expoLocalizationModule } from './module';
2
+ export { type ExpoLocalizationModuleConfig, parseExpoLocalizationModuleConfig } from './config';
3
+ export { EXPO_LOCALIZATION_MODULE_ID, expoLocalizationModule } from './module';
4
+ export default expoLocalizationModule;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAElD,OAAO,EAAE,KAAK,4BAA4B,EAAE,iCAAiC,EAAE,MAAM,UAAU,CAAC;AAChG,OAAO,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAE/E,eAAe,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { expoLocalizationModule } from './module';
2
+ export { parseExpoLocalizationModuleConfig } from './config';
3
+ export { EXPO_LOCALIZATION_MODULE_ID, expoLocalizationModule } from './module';
4
+ export default expoLocalizationModule;
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAElD,OAAO,EAAqC,iCAAiC,EAAE,MAAM,UAAU,CAAC;AAChG,OAAO,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAE/E,eAAe,sBAAsB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { type ModuleDefinition } from '@ankhorage/orchestrator';
2
+ import { type ExpoLocalizationModuleConfig } from './config';
3
+ export declare const EXPO_LOCALIZATION_MODULE_ID = "expo-localization";
4
+ export declare const expoLocalizationModule: ModuleDefinition<ExpoLocalizationModuleConfig>;
5
+ //# sourceMappingURL=module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEjG,OAAO,EAAE,KAAK,4BAA4B,EAAqC,MAAM,UAAU,CAAC;AAGhG,eAAO,MAAM,2BAA2B,sBAAsB,CAAC;AAE/D,eAAO,MAAM,sBAAsB,EAAE,gBAAgB,CAAC,4BAA4B,CAgE9E,CAAC"}
package/dist/module.js ADDED
@@ -0,0 +1,68 @@
1
+ import { defineModule } from '@ankhorage/orchestrator';
2
+ import { parseExpoLocalizationModuleConfig } from './config';
3
+ import { buildLocalizationWriteFiles } from './templateFiles';
4
+ export const EXPO_LOCALIZATION_MODULE_ID = 'expo-localization';
5
+ export const expoLocalizationModule = defineModule({
6
+ id: EXPO_LOCALIZATION_MODULE_ID,
7
+ plan(context) {
8
+ const config = parseExpoLocalizationModuleConfig(context.config);
9
+ return [
10
+ {
11
+ type: 'ensure-packages',
12
+ add: [
13
+ { name: 'i18next', version: '^25.8.10' },
14
+ { name: 'react-i18next', version: '^16.5.4' },
15
+ { name: 'expo-localization', version: '~17.0.8' },
16
+ ],
17
+ },
18
+ {
19
+ type: 'write-files',
20
+ files: buildLocalizationWriteFiles({
21
+ defaultLocale: config.defaultLocale,
22
+ locales: config.locales,
23
+ translations: config.translations,
24
+ }),
25
+ },
26
+ {
27
+ type: 'patch-text-block',
28
+ path: 'src/app/_layout.tsx',
29
+ blockId: `${EXPO_LOCALIZATION_MODULE_ID}:root-layout-import`,
30
+ content: 'import { LocalizationPluginProvider } from "@/plugins/localization";',
31
+ anchor: {
32
+ find: "import ankhConfig from '@root/ankh.config.json';",
33
+ position: 'before',
34
+ },
35
+ },
36
+ {
37
+ type: 'patch-text-block',
38
+ path: 'src/app/_layout.tsx',
39
+ blockId: `${EXPO_LOCALIZATION_MODULE_ID}:root-layout-provider`,
40
+ content: ' output = <LocalizationPluginProvider>{output}</LocalizationPluginProvider>;',
41
+ anchor: {
42
+ find: ' return (',
43
+ position: 'before',
44
+ },
45
+ },
46
+ {
47
+ type: 'patch-text-block',
48
+ path: 'app.config.ts',
49
+ blockId: `${EXPO_LOCALIZATION_MODULE_ID}:expo-plugin`,
50
+ content: ' "expo-localization",',
51
+ anchor: {
52
+ find: 'plugins: [',
53
+ position: 'after',
54
+ },
55
+ },
56
+ {
57
+ type: 'json-set',
58
+ path: 'ankh.config.json',
59
+ jsonPath: 'settings.localization',
60
+ value: {
61
+ defaultLocale: config.defaultLocale,
62
+ locales: config.locales,
63
+ },
64
+ },
65
+ ];
66
+ },
67
+ });
68
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA4C,MAAM,yBAAyB,CAAC;AAEjG,OAAO,EAAqC,iCAAiC,EAAE,MAAM,UAAU,CAAC;AAChG,OAAO,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,CAAC,MAAM,2BAA2B,GAAG,mBAAmB,CAAC;AAE/D,MAAM,CAAC,MAAM,sBAAsB,GACjC,YAAY,CAA+B;IACzC,EAAE,EAAE,2BAA2B;IAC/B,IAAI,CAAC,OAAO;QACV,MAAM,MAAM,GAAG,iCAAiC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEjE,OAAO;YACL;gBACE,IAAI,EAAE,iBAAiB;gBACvB,GAAG,EAAE;oBACH,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE;oBACxC,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE;oBAC7C,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE;iBAClD;aACF;YACD;gBACE,IAAI,EAAE,aAAa;gBACnB,KAAK,EAAE,2BAA2B,CAAC;oBACjC,aAAa,EAAE,MAAM,CAAC,aAAa;oBACnC,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,YAAY,EAAE,MAAM,CAAC,YAAY;iBAClC,CAAC;aACH;YACD;gBACE,IAAI,EAAE,kBAAkB;gBACxB,IAAI,EAAE,qBAAqB;gBAC3B,OAAO,EAAE,GAAG,2BAA2B,qBAAqB;gBAC5D,OAAO,EAAE,sEAAsE;gBAC/E,MAAM,EAAE;oBACN,IAAI,EAAE,kDAAkD;oBACxD,QAAQ,EAAE,QAAQ;iBACnB;aACF;YACD;gBACE,IAAI,EAAE,kBAAkB;gBACxB,IAAI,EAAE,qBAAqB;gBAC3B,OAAO,EAAE,GAAG,2BAA2B,uBAAuB;gBAC9D,OAAO,EAAE,+EAA+E;gBACxF,MAAM,EAAE;oBACN,IAAI,EAAE,YAAY;oBAClB,QAAQ,EAAE,QAAQ;iBACnB;aACF;YACD;gBACE,IAAI,EAAE,kBAAkB;gBACxB,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,GAAG,2BAA2B,cAAc;gBACrD,OAAO,EAAE,0BAA0B;gBACnC,MAAM,EAAE;oBACN,IAAI,EAAE,YAAY;oBAClB,QAAQ,EAAE,OAAO;iBAClB;aACF;YACD;gBACE,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,kBAAkB;gBACxB,QAAQ,EAAE,uBAAuB;gBACjC,KAAK,EAAE;oBACL,aAAa,EAAE,MAAM,CAAC,aAAa;oBACnC,OAAO,EAAE,MAAM,CAAC,OAAO;iBACxB;aACF;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { WriteFileInstruction } from '@ankhorage/orchestrator';
2
+ export declare function buildLocalizationWriteFiles(args: {
3
+ defaultLocale: string;
4
+ locales: string[];
5
+ translations: Record<string, Record<string, string>>;
6
+ }): WriteFileInstruction[];
7
+ //# sourceMappingURL=templateFiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templateFiles.d.ts","sourceRoot":"","sources":["../src/templateFiles.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AASpE,wBAAgB,2BAA2B,CAAC,IAAI,EAAE;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACtD,GAAG,oBAAoB,EAAE,CAoCzB"}
@@ -0,0 +1,76 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const TEMPLATE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../templates');
5
+ const templateCache = new Map();
6
+ const defaultTranslations = {
7
+ de: readJsonTemplate('locales/de.json.tpl'),
8
+ en: readJsonTemplate('locales/en.json.tpl'),
9
+ };
10
+ export function buildLocalizationWriteFiles(args) {
11
+ const { defaultLocale, locales, translations } = args;
12
+ return [
13
+ {
14
+ path: 'src/plugins/localization/i18n.ts',
15
+ content: renderTemplate('i18n.ts.tpl', {
16
+ DEFAULT_LOCALE: JSON.stringify(defaultLocale),
17
+ }),
18
+ overwrite: true,
19
+ },
20
+ {
21
+ path: 'src/plugins/localization/useT.ts',
22
+ content: readTemplate('useT.ts.tpl'),
23
+ overwrite: true,
24
+ },
25
+ {
26
+ path: 'src/plugins/localization/LocalizationProvider.tsx',
27
+ content: renderTemplate('LocalizationProvider.tsx.tpl', {
28
+ DEFAULT_LOCALE: JSON.stringify(defaultLocale),
29
+ RESOURCE_LINES: locales
30
+ .map((locale) => ` ${JSON.stringify(locale)}: { translation: require("./locales/${locale}.json") },`)
31
+ .join('\n'),
32
+ }),
33
+ overwrite: true,
34
+ },
35
+ {
36
+ path: 'src/plugins/localization/index.ts',
37
+ content: readTemplate('index.ts.tpl'),
38
+ overwrite: true,
39
+ },
40
+ ...buildLocaleJsonFiles(locales, translations),
41
+ ];
42
+ }
43
+ function buildLocaleJsonFiles(locales, configTranslations) {
44
+ return locales.map((locale) => {
45
+ const contentSource = configTranslations[locale] ??
46
+ (locale.startsWith('de') ? defaultTranslations.de : defaultTranslations.en);
47
+ return {
48
+ path: `src/plugins/localization/locales/${locale}.json`,
49
+ content: `${JSON.stringify(contentSource, null, 2)}\n`,
50
+ };
51
+ });
52
+ }
53
+ function renderTemplate(templateName, replacements) {
54
+ let content = readTemplate(templateName);
55
+ for (const [token, value] of Object.entries(replacements)) {
56
+ content = content.replaceAll(`__${token}__`, value);
57
+ }
58
+ return content;
59
+ }
60
+ function readJsonTemplate(templateName) {
61
+ return JSON.parse(readTemplate(templateName));
62
+ }
63
+ function readTemplate(templateName) {
64
+ const cached = templateCache.get(templateName);
65
+ if (cached) {
66
+ return cached;
67
+ }
68
+ const templatePath = path.join(TEMPLATE_DIR, templateName);
69
+ if (!fs.existsSync(templatePath)) {
70
+ throw new Error(`Missing localization template: ${templateName}`);
71
+ }
72
+ const content = fs.readFileSync(templatePath, 'utf8');
73
+ templateCache.set(templateName, content);
74
+ return content;
75
+ }
76
+ //# sourceMappingURL=templateFiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templateFiles.js","sourceRoot":"","sources":["../src/templateFiles.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@ankhorage/orchestrator-module-expo-localization",
3
+ "version": "0.1.1",
4
+ "description": "Expo localization module for @ankhorage/orchestrator.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "sideEffects": false,
9
+ "files": [
10
+ "dist",
11
+ "templates",
12
+ "README.md",
13
+ "CHANGELOG.md",
14
+ "LICENSE"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "build": "rm -rf dist tsconfig.tsbuildinfo && bun x tsc -p tsconfig.json",
24
+ "lint": "eslint . --max-warnings=0",
25
+ "lint:fix": "eslint . --fix --max-warnings=0",
26
+ "format": "prettier --write .",
27
+ "format:check": "prettier --check .",
28
+ "knip": "knip",
29
+ "test": "bun test",
30
+ "changeset": "changeset",
31
+ "changeset:version": "changeset version"
32
+ },
33
+ "dependencies": {
34
+ "@ankhorage/orchestrator": "^0.1.1"
35
+ },
36
+ "devDependencies": {
37
+ "@ankhorage/devtools": "^1.0.0",
38
+ "@changesets/cli": "^2.30.0",
39
+ "@types/bun": "^1.3.11",
40
+ "@types/node": "^25.2.3",
41
+ "eslint": "^10.2.0",
42
+ "knip": "^5.83.1",
43
+ "prettier": "^3.8.1",
44
+ "typescript": "^5.9.3"
45
+ },
46
+ "packageManager": "bun@1.3.11",
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+ }
@@ -0,0 +1,53 @@
1
+ /* generated by @ankhorage/orchestrator-module-expo-localization */
2
+ import React, { useEffect, useState } from "react";
3
+ import * as ExpoLocalization from "expo-localization";
4
+ import { useTranslation } from "react-i18next";
5
+
6
+ import { TranslationProvider } from "@ankhorage/surface";
7
+
8
+ import { initI18n } from "./i18n";
9
+
10
+ export interface LocalizationPluginConfig {
11
+ defaultLocale?: string;
12
+ }
13
+
14
+ export function LocalizationPluginProvider(props: {
15
+ children: React.ReactNode;
16
+ config?: LocalizationPluginConfig;
17
+ }) {
18
+ const { children, config } = props;
19
+
20
+ const locales = ExpoLocalization.getLocales();
21
+ const deviceLocale =
22
+ (locales.length > 0 ? locales[0]?.languageTag : null) ??
23
+ (ExpoLocalization as { locale?: string }).locale ??
24
+ __DEFAULT_LOCALE__;
25
+
26
+ const initialLocale = config?.defaultLocale ?? deviceLocale;
27
+ const [currentLocale, setCurrentLocale] = useState<string>(initialLocale);
28
+
29
+ useEffect(() => {
30
+ const resources: Record<string, { translation: Record<string, string> }> = {
31
+ __RESOURCE_LINES__
32
+ };
33
+
34
+ initI18n({
35
+ locale: currentLocale,
36
+ resources
37
+ });
38
+ }, [currentLocale]);
39
+
40
+ const { t, i18n } = useTranslation();
41
+
42
+ useEffect(() => {
43
+ if (i18n.language && i18n.language !== currentLocale) {
44
+ setCurrentLocale(i18n.language);
45
+ }
46
+ }, [currentLocale, i18n.language]);
47
+
48
+ return (
49
+ <TranslationProvider t={i18n.isInitialized ? t : (key: string) => key} i18n={i18n}>
50
+ {children}
51
+ </TranslationProvider>
52
+ );
53
+ }
@@ -0,0 +1,42 @@
1
+ /* generated by @ankhorage/orchestrator-module-expo-localization */
2
+ import i18n from "i18next";
3
+ import { initReactI18next } from "react-i18next";
4
+
5
+ export type I18nResources = Record<string, { translation: Record<string, string> }>;
6
+
7
+ function normalizeLocale(requested: string, resources: I18nResources, fallback: string) {
8
+ const supported = Object.keys(resources);
9
+
10
+ if (supported.includes(requested)) return requested;
11
+
12
+ const [base] = requested.split("-");
13
+ if (base && supported.includes(base)) return base;
14
+
15
+ if (supported.includes(fallback)) return fallback;
16
+
17
+ return supported[0] ?? fallback;
18
+ }
19
+
20
+ export function initI18n(args: { locale: string; resources: I18nResources }) {
21
+ const { locale: requestedLocale, resources } = args;
22
+ const locale = normalizeLocale(requestedLocale, resources, __DEFAULT_LOCALE__);
23
+
24
+ if (!i18n.isInitialized) {
25
+ i18n.use(initReactI18next).init({
26
+ resources,
27
+ lng: locale,
28
+ fallbackLng: __DEFAULT_LOCALE__,
29
+ interpolation: { escapeValue: false },
30
+ react: { useSuspense: false }
31
+ });
32
+ return i18n;
33
+ }
34
+
35
+ if (i18n.language !== locale) {
36
+ void i18n.changeLanguage(locale);
37
+ }
38
+
39
+ return i18n;
40
+ }
41
+
42
+ export { i18n };
@@ -0,0 +1,4 @@
1
+ /* generated by @ankhorage/orchestrator-module-expo-localization */
2
+ export { LocalizationPluginProvider } from "./LocalizationProvider";
3
+ export { useT } from "./useT";
4
+ export { i18n } from "./i18n";
@@ -0,0 +1,6 @@
1
+ {
2
+ "app_title": "Meine App",
3
+ "hello": "Hallo",
4
+ "system_overview": "Systemübersicht",
5
+ "system-overview": "Systemübersicht"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "app_title": "My App",
3
+ "hello": "Hello",
4
+ "system_overview": "System Overview",
5
+ "system-overview": "System Overview"
6
+ }
@@ -0,0 +1,7 @@
1
+ /* generated by @ankhorage/orchestrator-module-expo-localization */
2
+ import { useTranslation } from "react-i18next";
3
+
4
+ export function useT(namespace: string = "translation") {
5
+ const { t, i18n } = useTranslation(namespace);
6
+ return { t, i18n };
7
+ }