i18next-cli 1.47.8 → 1.47.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/instrumenter/core/instrumenter.js +251 -127
- package/dist/cjs/locize.js +40 -23
- package/dist/esm/cli.js +1 -1
- package/dist/esm/instrumenter/core/instrumenter.js +251 -127
- package/dist/esm/locize.js +40 -23
- package/package.json +1 -1
- package/types/instrumenter/core/instrumenter.d.ts.map +1 -1
- package/types/locize.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -249,7 +249,7 @@ npx i18next-cli lint
|
|
|
249
249
|
|
|
250
250
|
### `instrument`
|
|
251
251
|
|
|
252
|
-
Scans your source code for hardcoded user-facing strings and instruments them with i18next translation calls. This is useful for adding i18next instrumentation to an existing codebase that wasn't built with internationalization in mind.
|
|
252
|
+
Scans your source code for hardcoded user-facing strings and instruments them with i18next translation calls. This is useful for adding i18next instrumentation to an existing codebase that wasn't built with internationalization in mind. You can see this in action in [this video](https://youtu.be/aWZnZXwGg34) or in [this blog post](https://www.locize.com/blog/i18next-cli-instrument).
|
|
253
253
|
|
|
254
254
|
> **⚠️ First-Step Tool:** The `instrument` command uses heuristic-based detection and is designed as a **first pass** to identify and suggest transformation candidates. It will **not catch 100% of cases**, and you should expect both false positives and false negatives. Always review the suggested transformations carefully before committing them to your codebase. Think of it as an intelligent code assistant, not an automated compiler.
|
|
255
255
|
|
package/dist/cjs/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ const program = new commander.Command();
|
|
|
31
31
|
program
|
|
32
32
|
.name('i18next-cli')
|
|
33
33
|
.description('A unified, high-performance i18next CLI.')
|
|
34
|
-
.version('1.47.
|
|
34
|
+
.version('1.47.10'); // This string is replaced with the actual version at build time by rollup
|
|
35
35
|
// new: global config override option
|
|
36
36
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
37
37
|
program
|
|
@@ -39,6 +39,7 @@ async function runInstrumenter(config, options, logger$1 = new logger.ConsoleLog
|
|
|
39
39
|
let totalTransformed = 0;
|
|
40
40
|
let totalSkipped = 0;
|
|
41
41
|
let totalLanguageChanges = 0;
|
|
42
|
+
let usesI18nextT = false;
|
|
42
43
|
// Detect framework and language
|
|
43
44
|
const hasReact = await isProjectUsingReact();
|
|
44
45
|
const hasTypeScript = await isProjectUsingTypeScript();
|
|
@@ -157,6 +158,10 @@ async function runInstrumenter(config, options, logger$1 = new logger.ConsoleLog
|
|
|
157
158
|
totalTransformed += transformResult.transformCount;
|
|
158
159
|
totalLanguageChanges += transformResult.languageChangeCount;
|
|
159
160
|
totalSkipped += candidates.length - approvedCandidates.length;
|
|
161
|
+
// Track whether any non-component candidate was transformed (i.e. i18next.t() was used)
|
|
162
|
+
if (!usesI18nextT && approvedCandidates.some(c => !c.insideComponent && c.confidence >= 0.7)) {
|
|
163
|
+
usesI18nextT = true;
|
|
164
|
+
}
|
|
160
165
|
// Log any warnings (e.g. i18next.t() in React files)
|
|
161
166
|
if (transformResult.warnings?.length) {
|
|
162
167
|
for (const warning of transformResult.warnings) {
|
|
@@ -181,7 +186,7 @@ async function runInstrumenter(config, options, logger$1 = new logger.ConsoleLog
|
|
|
181
186
|
spinner.succeed(node_util.styleText('bold', `Scanned complete: ${totalCandidates} candidates, ${totalTransformed} approved${langChangeSuffix}`));
|
|
182
187
|
// Generate i18n init file if needed and any transformations were made
|
|
183
188
|
if ((totalTransformed > 0 || totalLanguageChanges > 0) && !options.isDryRun) {
|
|
184
|
-
const initFilePath = await ensureI18nInitFile(hasReact, hasTypeScript, config, logger$1);
|
|
189
|
+
const initFilePath = await ensureI18nInitFile(hasReact, hasTypeScript, config, logger$1, usesI18nextT);
|
|
185
190
|
if (initFilePath) {
|
|
186
191
|
await injectI18nImportIntoEntryFile(initFilePath, logger$1);
|
|
187
192
|
}
|
|
@@ -1384,6 +1389,70 @@ async function isProjectUsingReact() {
|
|
|
1384
1389
|
return false;
|
|
1385
1390
|
}
|
|
1386
1391
|
}
|
|
1392
|
+
/** Well-known frontend framework packages (presence → browser environment). */
|
|
1393
|
+
const FRONTEND_FRAMEWORKS = [
|
|
1394
|
+
'react', 'react-i18next', 'vue', 'vue-i18next',
|
|
1395
|
+
'@angular/core', 'angular-i18next',
|
|
1396
|
+
'svelte', 'svelte-i18next',
|
|
1397
|
+
'preact', 'solid-js', 'jquery', 'lit', 'ember-source', 'stimulus',
|
|
1398
|
+
'next', 'nuxt', 'gatsby', '@remix-run/react', 'astro'
|
|
1399
|
+
];
|
|
1400
|
+
/** Well-known bundlers whose presence implies a browser build target. */
|
|
1401
|
+
const BUNDLERS = [
|
|
1402
|
+
'webpack', 'vite', '@vitejs/plugin-react', 'rollup', 'parcel',
|
|
1403
|
+
'esbuild', 'turbopack', 'snowpack'
|
|
1404
|
+
];
|
|
1405
|
+
/** Edge/serverless markers (no filesystem access). */
|
|
1406
|
+
const EDGE_MARKERS = [
|
|
1407
|
+
'@cloudflare/workers-types', 'wrangler', '@cloudflare/next-on-pages',
|
|
1408
|
+
'@vercel/edge', '@netlify/edge-functions', '@deno/kv'
|
|
1409
|
+
];
|
|
1410
|
+
/** Well-known Node.js server frameworks. */
|
|
1411
|
+
const SERVER_FRAMEWORKS = [
|
|
1412
|
+
'express', 'fastify', 'koa', 'hapi', '@hapi/hapi',
|
|
1413
|
+
'@nestjs/core', 'restify', 'micro', 'polka', 'h3'
|
|
1414
|
+
];
|
|
1415
|
+
/**
|
|
1416
|
+
* Analyses `package.json` dependencies (and a few project-root files) to
|
|
1417
|
+
* classify the project's runtime environment.
|
|
1418
|
+
*
|
|
1419
|
+
* Priority order:
|
|
1420
|
+
* 1. Edge / serverless markers → `'edge'` (no filesystem)
|
|
1421
|
+
* 2. Frontend framework or bundler → `'browser'`
|
|
1422
|
+
* 3. Node.js server framework → `'node-server'`
|
|
1423
|
+
* 4. Fallback → `'unknown'`
|
|
1424
|
+
*/
|
|
1425
|
+
async function detectProjectEnvironment() {
|
|
1426
|
+
try {
|
|
1427
|
+
const packageJsonPath = process.cwd() + '/package.json';
|
|
1428
|
+
const raw = await promises.readFile(packageJsonPath, 'utf-8');
|
|
1429
|
+
const packageJson = JSON.parse(raw);
|
|
1430
|
+
const allDeps = {
|
|
1431
|
+
...packageJson.dependencies,
|
|
1432
|
+
...packageJson.devDependencies
|
|
1433
|
+
};
|
|
1434
|
+
const has = (list) => list.some(dep => !!allDeps[dep]);
|
|
1435
|
+
// 1. Edge / serverless (check first — these projects may also list a
|
|
1436
|
+
// bundler or even a framework, but they have no filesystem)
|
|
1437
|
+
if (has(EDGE_MARKERS))
|
|
1438
|
+
return 'edge';
|
|
1439
|
+
// Also check for wrangler.toml / wrangler.json
|
|
1440
|
+
const cwd = process.cwd();
|
|
1441
|
+
if (await fileExists(node_path.join(cwd, 'wrangler.toml')) || await fileExists(node_path.join(cwd, 'wrangler.json'))) {
|
|
1442
|
+
return 'edge';
|
|
1443
|
+
}
|
|
1444
|
+
// 2. Browser / frontend
|
|
1445
|
+
if (has(FRONTEND_FRAMEWORKS) || has(BUNDLERS))
|
|
1446
|
+
return 'browser';
|
|
1447
|
+
// 3. Node.js server
|
|
1448
|
+
if (has(SERVER_FRAMEWORKS))
|
|
1449
|
+
return 'node-server';
|
|
1450
|
+
return 'unknown';
|
|
1451
|
+
}
|
|
1452
|
+
catch {
|
|
1453
|
+
return 'unknown';
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1387
1456
|
/**
|
|
1388
1457
|
* Checks if the project uses TypeScript (looks for tsconfig.json).
|
|
1389
1458
|
*/
|
|
@@ -1434,14 +1503,12 @@ function buildDynamicImportPath(outputTemplate, initDir) {
|
|
|
1434
1503
|
* Ensures that an i18n initialization file exists in the project.
|
|
1435
1504
|
* If no existing init file is found, generates a sensible default.
|
|
1436
1505
|
*
|
|
1437
|
-
*
|
|
1438
|
-
* i18next-resources-to-backend
|
|
1439
|
-
*
|
|
1440
|
-
*
|
|
1441
|
-
* For React projects: creates i18n.ts with react-i18next integration.
|
|
1442
|
-
* For non-React projects: creates i18n.ts with basic i18next init.
|
|
1506
|
+
* The generated file's backend strategy depends on the project context:
|
|
1507
|
+
* - React app without i18next.t() → `i18next-resources-to-backend` (async dynamic imports)
|
|
1508
|
+
* - React app with i18next.t() → bundled resources (static imports, synchronous)
|
|
1509
|
+
* - Server-side (no React) → `i18next-fs-backend` (filesystem, initImmediate: false + preload)
|
|
1443
1510
|
*/
|
|
1444
|
-
async function ensureI18nInitFile(hasReact, hasTypeScript, config, logger) {
|
|
1511
|
+
async function ensureI18nInitFile(hasReact, hasTypeScript, config, logger, usesI18nextT) {
|
|
1445
1512
|
const cwd = process.cwd();
|
|
1446
1513
|
// Check for existing init files in common locations
|
|
1447
1514
|
const searchDirs = ['src', '.'];
|
|
@@ -1475,125 +1542,17 @@ async function ensureI18nInitFile(hasReact, hasTypeScript, config, logger) {
|
|
|
1475
1542
|
const initDir = srcExists ? node_path.join(cwd, 'src') : cwd;
|
|
1476
1543
|
const initFileExt = hasTypeScript ? '.ts' : '.js';
|
|
1477
1544
|
const initFilePath = node_path.join(initDir, 'i18n' + initFileExt);
|
|
1478
|
-
const
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1481
|
-
const
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
}
|
|
1489
|
-
const initBlock = initOptions.join(',\n');
|
|
1490
|
-
let initContent;
|
|
1491
|
-
if (typeof config.extract.output === 'string') {
|
|
1492
|
-
// Derive a dynamic import path so the generated init file loads translations automatically
|
|
1493
|
-
const dynamicImportPath = buildDynamicImportPath(config.extract.output, initDir);
|
|
1494
|
-
const importPathTemplate = dynamicImportPath
|
|
1495
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
1496
|
-
.replace(/\{\{language\}\}|\{\{lng\}\}/g, '${language}')
|
|
1497
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
1498
|
-
.replace(/\{\{namespace\}\}/g, '${namespace}');
|
|
1499
|
-
const hasNamespace = config.extract.output.includes('{{namespace}}');
|
|
1500
|
-
const callbackParams = hasNamespace ? hasTypeScript ? 'language: string, namespace: string' : 'language, namespace' : hasTypeScript ? 'language: string' : 'language';
|
|
1501
|
-
const backendUseLine = ' .use(resourcesToBackend((' + callbackParams + ') => import(`' + importPathTemplate + '`)))';
|
|
1502
|
-
if (hasReact) {
|
|
1503
|
-
initContent = `// Generated by i18next-cli — review and adapt to your project's needs.
|
|
1504
|
-
// You may need to install dependencies: npm install i18next react-i18next i18next-resources-to-backend
|
|
1505
|
-
//
|
|
1506
|
-
// Other translation loading approaches:
|
|
1507
|
-
// • Static imports or bundled JSON: https://www.i18next.com/how-to/add-or-load-translations
|
|
1508
|
-
// • Lazy-load from a server: https://github.com/i18next/i18next-http-backend
|
|
1509
|
-
// • Manage translations with your team via Locize: https://www.locize.com
|
|
1510
|
-
// (see i18next-locize-backend: https://github.com/locize/i18next-locize-backend)
|
|
1511
|
-
import i18next from 'i18next'
|
|
1512
|
-
import { initReactI18next } from 'react-i18next'
|
|
1513
|
-
import resourcesToBackend from 'i18next-resources-to-backend'
|
|
1514
|
-
|
|
1515
|
-
i18next
|
|
1516
|
-
.use(initReactI18next)
|
|
1517
|
-
${backendUseLine}
|
|
1518
|
-
.init({
|
|
1519
|
-
${initBlock}
|
|
1520
|
-
})
|
|
1521
|
-
|
|
1522
|
-
export default i18next
|
|
1523
|
-
`;
|
|
1524
|
-
}
|
|
1525
|
-
else {
|
|
1526
|
-
initContent = `// Generated by i18next-cli — review and adapt to your project's needs.
|
|
1527
|
-
// You may need to install the dependency: npm install i18next i18next-resources-to-backend
|
|
1528
|
-
//
|
|
1529
|
-
// Other translation loading approaches:
|
|
1530
|
-
// • Static imports or bundled JSON: https://www.i18next.com/how-to/add-or-load-translations
|
|
1531
|
-
// • Lazy-load from a server: https://github.com/i18next/i18next-http-backend
|
|
1532
|
-
// • Manage translations with your team via Locize: https://www.locize.com
|
|
1533
|
-
// (see i18next-locize-backend: https://github.com/locize/i18next-locize-backend)
|
|
1534
|
-
import i18next from 'i18next'
|
|
1535
|
-
import resourcesToBackend from 'i18next-resources-to-backend'
|
|
1536
|
-
|
|
1537
|
-
i18next
|
|
1538
|
-
${backendUseLine}
|
|
1539
|
-
.init({
|
|
1540
|
-
${initBlock}
|
|
1541
|
-
})
|
|
1542
|
-
|
|
1543
|
-
export default i18next
|
|
1544
|
-
`;
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
else {
|
|
1548
|
-
// Output is a function — can't derive import path, fall back to comments only
|
|
1549
|
-
if (hasReact) {
|
|
1550
|
-
initContent = `// Generated by i18next-cli — review and adapt to your project's needs.
|
|
1551
|
-
// You may need to install dependencies: npm install i18next react-i18next
|
|
1552
|
-
//
|
|
1553
|
-
// Loading translations:
|
|
1554
|
-
// • Static imports or bundled JSON: https://www.i18next.com/how-to/add-or-load-translations
|
|
1555
|
-
// • Lazy-load in memory with dynamic imports: https://github.com/i18next/i18next-resources-to-backend
|
|
1556
|
-
// • Lazy-load from a backend: https://github.com/i18next/i18next-http-backend
|
|
1557
|
-
// • Manage translations with your team via Locize: https://www.locize.com
|
|
1558
|
-
// (see i18next-locize-backend: https://github.com/locize/i18next-locize-backend)
|
|
1559
|
-
import i18next from 'i18next'
|
|
1560
|
-
import { initReactI18next } from 'react-i18next'
|
|
1561
|
-
|
|
1562
|
-
i18next
|
|
1563
|
-
.use(initReactI18next)
|
|
1564
|
-
.init({
|
|
1565
|
-
returnEmptyString: false, // allows empty string as valid translation
|
|
1566
|
-
// lng: ${config.locales.at(-1)}, // or add a language detector to detect the preferred language of your user
|
|
1567
|
-
fallbackLng: '${primaryLang}'
|
|
1568
|
-
// resources: { ... } — or use a backend plugin to load translations
|
|
1569
|
-
})
|
|
1570
|
-
|
|
1571
|
-
export default i18next
|
|
1572
|
-
`;
|
|
1573
|
-
}
|
|
1574
|
-
else {
|
|
1575
|
-
initContent = `// Generated by i18next-cli — review and adapt to your project's needs.
|
|
1576
|
-
// You may need to install the dependency: npm install i18next
|
|
1577
|
-
//
|
|
1578
|
-
// Loading translations:
|
|
1579
|
-
// • Static imports or bundled JSON: https://www.i18next.com/how-to/add-or-load-translations
|
|
1580
|
-
// • Lazy-load in memory with dynamic imports: https://github.com/i18next/i18next-resources-to-backend
|
|
1581
|
-
// • Lazy-load from a backend: https://github.com/i18next/i18next-http-backend
|
|
1582
|
-
// • Manage translations with your team via Locize: https://www.locize.com
|
|
1583
|
-
// (see i18next-locize-backend: https://github.com/locize/i18next-locize-backend)
|
|
1584
|
-
import i18next from 'i18next'
|
|
1585
|
-
|
|
1586
|
-
i18next.init({
|
|
1587
|
-
returnEmptyString: false, // allows empty string as valid translation
|
|
1588
|
-
// lng: ${config.locales.at(-1)}, // or add a language detector to detect the preferred language of your user
|
|
1589
|
-
fallbackLng: '${primaryLang}'
|
|
1590
|
-
// resources: { ... } — or use a backend plugin to load translations
|
|
1591
|
-
})
|
|
1592
|
-
|
|
1593
|
-
export default i18next
|
|
1594
|
-
`;
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1545
|
+
const environment = await detectProjectEnvironment();
|
|
1546
|
+
const strategy = determineBackendStrategy(environment, usesI18nextT);
|
|
1547
|
+
const outputTemplate = typeof config.extract.output === 'string' ? config.extract.output : null;
|
|
1548
|
+
const initContent = buildInitFileContent({
|
|
1549
|
+
strategy,
|
|
1550
|
+
hasReact,
|
|
1551
|
+
hasTypeScript,
|
|
1552
|
+
config,
|
|
1553
|
+
initDir,
|
|
1554
|
+
outputTemplate
|
|
1555
|
+
});
|
|
1597
1556
|
try {
|
|
1598
1557
|
await promises.mkdir(initDir, { recursive: true });
|
|
1599
1558
|
await promises.writeFile(initFilePath, initContent);
|
|
@@ -1605,6 +1564,171 @@ export default i18next
|
|
|
1605
1564
|
return null;
|
|
1606
1565
|
}
|
|
1607
1566
|
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Determines which backend strategy to use for the i18n init file.
|
|
1569
|
+
*
|
|
1570
|
+
* Decision logic:
|
|
1571
|
+
* 1. Node.js server with filesystem → `fs-backend`
|
|
1572
|
+
* (synchronous with `initImmediate: false` + `preload`)
|
|
1573
|
+
* 2. Browser / edge / unknown with `i18next.t()` outside React components →
|
|
1574
|
+
* `bundled-resources` (static imports so resources are available synchronously)
|
|
1575
|
+
* 3. Otherwise → `resources-to-backend` (async dynamic imports, lazy-loaded)
|
|
1576
|
+
*/
|
|
1577
|
+
function determineBackendStrategy(environment, usesI18nextT) {
|
|
1578
|
+
if (environment === 'node-server')
|
|
1579
|
+
return 'fs-backend';
|
|
1580
|
+
if (usesI18nextT)
|
|
1581
|
+
return 'bundled-resources';
|
|
1582
|
+
return 'resources-to-backend';
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Builds the full i18n init file content from composable parts,
|
|
1586
|
+
* avoiding repetition across different strategies.
|
|
1587
|
+
*/
|
|
1588
|
+
function buildInitFileContent(opts) {
|
|
1589
|
+
const { strategy, hasReact, hasTypeScript, config, initDir, outputTemplate } = opts;
|
|
1590
|
+
const primaryLang = config.extract.primaryLanguage ?? config.locales[0] ?? 'en';
|
|
1591
|
+
const defaultNS = config.extract.defaultNS !== false ? (config.extract.defaultNS || 'translation') : null;
|
|
1592
|
+
const ns = defaultNS || 'translation';
|
|
1593
|
+
// ── Dependencies for the install hint ──
|
|
1594
|
+
const deps = ['i18next'];
|
|
1595
|
+
if (hasReact)
|
|
1596
|
+
deps.push('react-i18next');
|
|
1597
|
+
if (strategy === 'resources-to-backend' && outputTemplate)
|
|
1598
|
+
deps.push('i18next-resources-to-backend');
|
|
1599
|
+
if (strategy === 'fs-backend' && outputTemplate)
|
|
1600
|
+
deps.push('i18next-fs-backend');
|
|
1601
|
+
const lines = [];
|
|
1602
|
+
// ── Header comment ──
|
|
1603
|
+
lines.push("// Generated by i18next-cli — review and adapt to your project's needs.", `// You may need to install dependencies: npm install ${deps.join(' ')}`, '//', '// Other translation loading approaches:', '// • Static imports or bundled JSON: https://www.i18next.com/how-to/add-or-load-translations', '// • Lazy-load from a server: https://github.com/i18next/i18next-http-backend', '// • Manage translations with your team via Locize: https://www.locize.com', '// (see i18next-locize-backend: https://github.com/locize/i18next-locize-backend)');
|
|
1604
|
+
// ── Import declarations ──
|
|
1605
|
+
lines.push("import i18next from 'i18next'");
|
|
1606
|
+
if (hasReact)
|
|
1607
|
+
lines.push("import { initReactI18next } from 'react-i18next'");
|
|
1608
|
+
if (outputTemplate) {
|
|
1609
|
+
switch (strategy) {
|
|
1610
|
+
case 'resources-to-backend':
|
|
1611
|
+
lines.push("import resourcesToBackend from 'i18next-resources-to-backend'");
|
|
1612
|
+
break;
|
|
1613
|
+
case 'bundled-resources':
|
|
1614
|
+
for (const locale of config.locales) {
|
|
1615
|
+
const importPath = buildResourceImportPath(outputTemplate, initDir, locale, ns);
|
|
1616
|
+
lines.push(`import ${toResourceVarName(locale, ns)} from '${importPath}'`);
|
|
1617
|
+
}
|
|
1618
|
+
break;
|
|
1619
|
+
case 'fs-backend':
|
|
1620
|
+
lines.push("import Backend from 'i18next-fs-backend'");
|
|
1621
|
+
lines.push("import { resolve, dirname } from 'node:path'");
|
|
1622
|
+
lines.push("import { fileURLToPath } from 'node:url'");
|
|
1623
|
+
break;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
// ── Pre-init statements ──
|
|
1627
|
+
lines.push('');
|
|
1628
|
+
if (strategy === 'fs-backend' && outputTemplate) {
|
|
1629
|
+
lines.push('const __dirname = dirname(fileURLToPath(import.meta.url))');
|
|
1630
|
+
lines.push('');
|
|
1631
|
+
}
|
|
1632
|
+
// ── .use() chain entries ──
|
|
1633
|
+
const useEntries = [];
|
|
1634
|
+
if (hasReact)
|
|
1635
|
+
useEntries.push(' .use(initReactI18next)');
|
|
1636
|
+
if (outputTemplate) {
|
|
1637
|
+
if (strategy === 'resources-to-backend') {
|
|
1638
|
+
const dynamicPath = buildDynamicImportPath(outputTemplate, initDir);
|
|
1639
|
+
const importPathTemplate = dynamicPath
|
|
1640
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
1641
|
+
.replace(/\{\{language\}\}|\{\{lng\}\}/g, '${language}')
|
|
1642
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
1643
|
+
.replace(/\{\{namespace\}\}/g, '${namespace}');
|
|
1644
|
+
const hasNamespace = outputTemplate.includes('{{namespace}}');
|
|
1645
|
+
const cbParams = hasNamespace
|
|
1646
|
+
? (hasTypeScript ? 'language: string, namespace: string' : 'language, namespace')
|
|
1647
|
+
: (hasTypeScript ? 'language: string' : 'language');
|
|
1648
|
+
useEntries.push(` .use(resourcesToBackend((${cbParams}) => import(\`${importPathTemplate}\`)))`);
|
|
1649
|
+
}
|
|
1650
|
+
else if (strategy === 'fs-backend') {
|
|
1651
|
+
useEntries.push(' .use(Backend)');
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
// Emit the i18next chain — use compact form if no .use() calls
|
|
1655
|
+
const awaitPrefix = (strategy === 'fs-backend' && outputTemplate) ? 'await ' : '';
|
|
1656
|
+
if (useEntries.length > 0) {
|
|
1657
|
+
lines.push(`${awaitPrefix}i18next`);
|
|
1658
|
+
lines.push(...useEntries);
|
|
1659
|
+
lines.push(' .init({');
|
|
1660
|
+
}
|
|
1661
|
+
else {
|
|
1662
|
+
lines.push(`${awaitPrefix}i18next.init({`);
|
|
1663
|
+
}
|
|
1664
|
+
// ── .init() options ──
|
|
1665
|
+
const initOpts = [];
|
|
1666
|
+
if (strategy === 'fs-backend' && outputTemplate) {
|
|
1667
|
+
initOpts.push(' initImmediate: false,');
|
|
1668
|
+
}
|
|
1669
|
+
initOpts.push(' returnEmptyString: false, // allows empty string as valid translation');
|
|
1670
|
+
initOpts.push(` // lng: '${config.locales.at(-1)}', // or add a language detector to detect the preferred language of your user`);
|
|
1671
|
+
initOpts.push(` fallbackLng: '${primaryLang}',`);
|
|
1672
|
+
if (defaultNS) {
|
|
1673
|
+
initOpts.push(` defaultNS: '${ns}',`);
|
|
1674
|
+
}
|
|
1675
|
+
// Strategy-specific init options
|
|
1676
|
+
if (outputTemplate) {
|
|
1677
|
+
if (strategy === 'bundled-resources') {
|
|
1678
|
+
initOpts.push(' resources: {');
|
|
1679
|
+
for (const locale of config.locales) {
|
|
1680
|
+
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(locale) ? locale : `'${locale}'`;
|
|
1681
|
+
initOpts.push(` ${key}: { ${ns}: ${toResourceVarName(locale, ns)} },`);
|
|
1682
|
+
}
|
|
1683
|
+
initOpts.push(' },');
|
|
1684
|
+
}
|
|
1685
|
+
else if (strategy === 'fs-backend') {
|
|
1686
|
+
const loadPath = buildFsBackendLoadPath(outputTemplate, initDir);
|
|
1687
|
+
initOpts.push(` preload: [${config.locales.map(l => `'${l}'`).join(', ')}],`);
|
|
1688
|
+
initOpts.push(' backend: {');
|
|
1689
|
+
initOpts.push(` loadPath: resolve(__dirname, '${loadPath}'),`);
|
|
1690
|
+
initOpts.push(' },');
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
else {
|
|
1694
|
+
// No concrete output path — user needs to configure loading manually
|
|
1695
|
+
initOpts.push(' // resources: { ... } — or use a backend plugin to load translations');
|
|
1696
|
+
}
|
|
1697
|
+
lines.push(initOpts.join('\n'));
|
|
1698
|
+
lines.push(' })');
|
|
1699
|
+
lines.push('');
|
|
1700
|
+
lines.push('export default i18next');
|
|
1701
|
+
lines.push('');
|
|
1702
|
+
return lines.join('\n');
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Resolves the import path for a specific locale/namespace resource file
|
|
1706
|
+
* (used by the bundled-resources strategy).
|
|
1707
|
+
*/
|
|
1708
|
+
function buildResourceImportPath(outputTemplate, initDir, locale, namespace) {
|
|
1709
|
+
const rel = buildDynamicImportPath(outputTemplate, initDir);
|
|
1710
|
+
return rel
|
|
1711
|
+
.replace(/\{\{language\}\}|\{\{lng\}\}/g, locale)
|
|
1712
|
+
.replace(/\{\{namespace\}\}|\{\{ns\}\}/g, namespace);
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Resolves the loadPath for i18next-fs-backend, using i18next's `{{lng}}`
|
|
1716
|
+
* and `{{ns}}` interpolation syntax.
|
|
1717
|
+
*/
|
|
1718
|
+
function buildFsBackendLoadPath(outputTemplate, initDir) {
|
|
1719
|
+
const rel = buildDynamicImportPath(outputTemplate, initDir);
|
|
1720
|
+
return rel
|
|
1721
|
+
.replace(/\{\{language\}\}/g, '{{lng}}')
|
|
1722
|
+
.replace(/\{\{namespace\}\}/g, '{{ns}}');
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Converts a locale + namespace pair to a valid JS variable name.
|
|
1726
|
+
* E.g. ('en', 'translation') → 'enTranslation', ('zh-CN', 'common') → 'zhCNCommon'
|
|
1727
|
+
*/
|
|
1728
|
+
function toResourceVarName(locale, namespace) {
|
|
1729
|
+
const sanitizedLocale = locale.replace(/[^a-zA-Z0-9]/g, '');
|
|
1730
|
+
return sanitizedLocale + namespace.charAt(0).toUpperCase() + namespace.slice(1);
|
|
1731
|
+
}
|
|
1608
1732
|
/**
|
|
1609
1733
|
* Common entry-point file names, checked in priority order.
|
|
1610
1734
|
*/
|
package/dist/cjs/locize.js
CHANGED
|
@@ -7,29 +7,37 @@ var inquirer = require('inquirer');
|
|
|
7
7
|
var node_path = require('node:path');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Resolves the locize-cli executable to use.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* Tries, in order:
|
|
13
|
+
* 1. A locally / globally installed `locize` binary
|
|
14
|
+
* 2. Falls back to `npx locize-cli` so it can be fetched on demand
|
|
13
15
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* ```
|
|
16
|
+
* If neither works the process exits with an error.
|
|
17
|
+
*
|
|
18
|
+
* @returns An object with `cmd` (the executable) and `prefixArgs` (extra args
|
|
19
|
+
* to prepend before the locize sub-command, e.g. `['locize-cli']`
|
|
20
|
+
* when running through npx).
|
|
20
21
|
*/
|
|
21
|
-
async function
|
|
22
|
+
async function resolveLocizeBin() {
|
|
23
|
+
// 1. Try a locally / globally installed binary
|
|
22
24
|
try {
|
|
23
25
|
await execa.execa('locize', ['--version']);
|
|
26
|
+
return { cmd: 'locize', prefixArgs: [] };
|
|
24
27
|
}
|
|
25
|
-
catch
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
catch {
|
|
29
|
+
// not found – continue
|
|
30
|
+
}
|
|
31
|
+
// 2. Fall back to npx
|
|
32
|
+
try {
|
|
33
|
+
console.log(node_util.styleText('yellow', '`locize` command not found – trying npx...'));
|
|
34
|
+
await execa.execa('npx', ['locize-cli', '--version']);
|
|
35
|
+
return { cmd: 'npx', prefixArgs: ['locize-cli'] };
|
|
32
36
|
}
|
|
37
|
+
catch {
|
|
38
|
+
// npx also failed
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
33
41
|
}
|
|
34
42
|
/**
|
|
35
43
|
* Interactive setup wizard for configuring Locize credentials.
|
|
@@ -236,14 +244,23 @@ function buildArgs(command, config, cliOptions) {
|
|
|
236
244
|
* ```
|
|
237
245
|
*/
|
|
238
246
|
async function runLocizeCommand(command, config, cliOptions = {}) {
|
|
239
|
-
await
|
|
247
|
+
const resolved = await resolveLocizeBin();
|
|
248
|
+
if (!resolved) {
|
|
249
|
+
console.error(node_util.styleText('red', 'Error: `locize-cli` command not found.'));
|
|
250
|
+
console.log(node_util.styleText('yellow', 'Please install it to use the Locize integration:'));
|
|
251
|
+
console.log(node_util.styleText('cyan', ' npm install -g locize-cli'));
|
|
252
|
+
console.log(node_util.styleText('yellow', 'Or make sure npx is available so it can be fetched on demand.'));
|
|
253
|
+
process.exit(1);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const { cmd, prefixArgs } = resolved;
|
|
240
257
|
const spinner = ora(`Running 'locize ${command}'...\n`).start();
|
|
241
258
|
let effectiveConfig = config;
|
|
242
259
|
try {
|
|
243
260
|
// 1. First attempt
|
|
244
|
-
const initialArgs = buildArgs(command, effectiveConfig, cliOptions);
|
|
245
|
-
console.log(node_util.styleText('cyan', `\nRunning 'locize ${maskArgs(initialArgs).join(' ')}'...`));
|
|
246
|
-
const result = await execa.execa(
|
|
261
|
+
const initialArgs = [...prefixArgs, ...buildArgs(command, effectiveConfig, cliOptions)];
|
|
262
|
+
console.log(node_util.styleText('cyan', `\nRunning 'locize ${maskArgs(initialArgs.slice(prefixArgs.length)).join(' ')}'...`));
|
|
263
|
+
const result = await execa.execa(cmd, initialArgs, { stdio: 'pipe' });
|
|
247
264
|
spinner.succeed(node_util.styleText('green', `'locize ${command}' completed successfully.`));
|
|
248
265
|
if (result?.stdout)
|
|
249
266
|
console.log(result.stdout); // Print captured output on success
|
|
@@ -259,9 +276,9 @@ async function runLocizeCommand(command, config, cliOptions = {}) {
|
|
|
259
276
|
spinner.start('Retrying with new credentials...');
|
|
260
277
|
try {
|
|
261
278
|
// 3. Retry attempt, rebuilding args with the NOW-UPDATED currentConfig object
|
|
262
|
-
const retryArgs = buildArgs(command, effectiveConfig, cliOptions);
|
|
263
|
-
console.log(node_util.styleText('cyan', `\nRunning 'locize ${maskArgs(retryArgs).join(' ')}'...`));
|
|
264
|
-
const result = await execa.execa(
|
|
279
|
+
const retryArgs = [...prefixArgs, ...buildArgs(command, effectiveConfig, cliOptions)];
|
|
280
|
+
console.log(node_util.styleText('cyan', `\nRunning 'locize ${maskArgs(retryArgs.slice(prefixArgs.length)).join(' ')}'...`));
|
|
281
|
+
const result = await execa.execa(cmd, retryArgs, { stdio: 'pipe' });
|
|
265
282
|
spinner.succeed(node_util.styleText('green', 'Retry successful!'));
|
|
266
283
|
if (result?.stdout)
|
|
267
284
|
console.log(result.stdout);
|
package/dist/esm/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ const program = new Command();
|
|
|
29
29
|
program
|
|
30
30
|
.name('i18next-cli')
|
|
31
31
|
.description('A unified, high-performance i18next CLI.')
|
|
32
|
-
.version('1.47.
|
|
32
|
+
.version('1.47.10'); // This string is replaced with the actual version at build time by rollup
|
|
33
33
|
// new: global config override option
|
|
34
34
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
35
35
|
program
|
|
@@ -37,6 +37,7 @@ async function runInstrumenter(config, options, logger = new ConsoleLogger()) {
|
|
|
37
37
|
let totalTransformed = 0;
|
|
38
38
|
let totalSkipped = 0;
|
|
39
39
|
let totalLanguageChanges = 0;
|
|
40
|
+
let usesI18nextT = false;
|
|
40
41
|
// Detect framework and language
|
|
41
42
|
const hasReact = await isProjectUsingReact();
|
|
42
43
|
const hasTypeScript = await isProjectUsingTypeScript();
|
|
@@ -155,6 +156,10 @@ async function runInstrumenter(config, options, logger = new ConsoleLogger()) {
|
|
|
155
156
|
totalTransformed += transformResult.transformCount;
|
|
156
157
|
totalLanguageChanges += transformResult.languageChangeCount;
|
|
157
158
|
totalSkipped += candidates.length - approvedCandidates.length;
|
|
159
|
+
// Track whether any non-component candidate was transformed (i.e. i18next.t() was used)
|
|
160
|
+
if (!usesI18nextT && approvedCandidates.some(c => !c.insideComponent && c.confidence >= 0.7)) {
|
|
161
|
+
usesI18nextT = true;
|
|
162
|
+
}
|
|
158
163
|
// Log any warnings (e.g. i18next.t() in React files)
|
|
159
164
|
if (transformResult.warnings?.length) {
|
|
160
165
|
for (const warning of transformResult.warnings) {
|
|
@@ -179,7 +184,7 @@ async function runInstrumenter(config, options, logger = new ConsoleLogger()) {
|
|
|
179
184
|
spinner.succeed(styleText('bold', `Scanned complete: ${totalCandidates} candidates, ${totalTransformed} approved${langChangeSuffix}`));
|
|
180
185
|
// Generate i18n init file if needed and any transformations were made
|
|
181
186
|
if ((totalTransformed > 0 || totalLanguageChanges > 0) && !options.isDryRun) {
|
|
182
|
-
const initFilePath = await ensureI18nInitFile(hasReact, hasTypeScript, config, logger);
|
|
187
|
+
const initFilePath = await ensureI18nInitFile(hasReact, hasTypeScript, config, logger, usesI18nextT);
|
|
183
188
|
if (initFilePath) {
|
|
184
189
|
await injectI18nImportIntoEntryFile(initFilePath, logger);
|
|
185
190
|
}
|
|
@@ -1382,6 +1387,70 @@ async function isProjectUsingReact() {
|
|
|
1382
1387
|
return false;
|
|
1383
1388
|
}
|
|
1384
1389
|
}
|
|
1390
|
+
/** Well-known frontend framework packages (presence → browser environment). */
|
|
1391
|
+
const FRONTEND_FRAMEWORKS = [
|
|
1392
|
+
'react', 'react-i18next', 'vue', 'vue-i18next',
|
|
1393
|
+
'@angular/core', 'angular-i18next',
|
|
1394
|
+
'svelte', 'svelte-i18next',
|
|
1395
|
+
'preact', 'solid-js', 'jquery', 'lit', 'ember-source', 'stimulus',
|
|
1396
|
+
'next', 'nuxt', 'gatsby', '@remix-run/react', 'astro'
|
|
1397
|
+
];
|
|
1398
|
+
/** Well-known bundlers whose presence implies a browser build target. */
|
|
1399
|
+
const BUNDLERS = [
|
|
1400
|
+
'webpack', 'vite', '@vitejs/plugin-react', 'rollup', 'parcel',
|
|
1401
|
+
'esbuild', 'turbopack', 'snowpack'
|
|
1402
|
+
];
|
|
1403
|
+
/** Edge/serverless markers (no filesystem access). */
|
|
1404
|
+
const EDGE_MARKERS = [
|
|
1405
|
+
'@cloudflare/workers-types', 'wrangler', '@cloudflare/next-on-pages',
|
|
1406
|
+
'@vercel/edge', '@netlify/edge-functions', '@deno/kv'
|
|
1407
|
+
];
|
|
1408
|
+
/** Well-known Node.js server frameworks. */
|
|
1409
|
+
const SERVER_FRAMEWORKS = [
|
|
1410
|
+
'express', 'fastify', 'koa', 'hapi', '@hapi/hapi',
|
|
1411
|
+
'@nestjs/core', 'restify', 'micro', 'polka', 'h3'
|
|
1412
|
+
];
|
|
1413
|
+
/**
|
|
1414
|
+
* Analyses `package.json` dependencies (and a few project-root files) to
|
|
1415
|
+
* classify the project's runtime environment.
|
|
1416
|
+
*
|
|
1417
|
+
* Priority order:
|
|
1418
|
+
* 1. Edge / serverless markers → `'edge'` (no filesystem)
|
|
1419
|
+
* 2. Frontend framework or bundler → `'browser'`
|
|
1420
|
+
* 3. Node.js server framework → `'node-server'`
|
|
1421
|
+
* 4. Fallback → `'unknown'`
|
|
1422
|
+
*/
|
|
1423
|
+
async function detectProjectEnvironment() {
|
|
1424
|
+
try {
|
|
1425
|
+
const packageJsonPath = process.cwd() + '/package.json';
|
|
1426
|
+
const raw = await readFile(packageJsonPath, 'utf-8');
|
|
1427
|
+
const packageJson = JSON.parse(raw);
|
|
1428
|
+
const allDeps = {
|
|
1429
|
+
...packageJson.dependencies,
|
|
1430
|
+
...packageJson.devDependencies
|
|
1431
|
+
};
|
|
1432
|
+
const has = (list) => list.some(dep => !!allDeps[dep]);
|
|
1433
|
+
// 1. Edge / serverless (check first — these projects may also list a
|
|
1434
|
+
// bundler or even a framework, but they have no filesystem)
|
|
1435
|
+
if (has(EDGE_MARKERS))
|
|
1436
|
+
return 'edge';
|
|
1437
|
+
// Also check for wrangler.toml / wrangler.json
|
|
1438
|
+
const cwd = process.cwd();
|
|
1439
|
+
if (await fileExists(join(cwd, 'wrangler.toml')) || await fileExists(join(cwd, 'wrangler.json'))) {
|
|
1440
|
+
return 'edge';
|
|
1441
|
+
}
|
|
1442
|
+
// 2. Browser / frontend
|
|
1443
|
+
if (has(FRONTEND_FRAMEWORKS) || has(BUNDLERS))
|
|
1444
|
+
return 'browser';
|
|
1445
|
+
// 3. Node.js server
|
|
1446
|
+
if (has(SERVER_FRAMEWORKS))
|
|
1447
|
+
return 'node-server';
|
|
1448
|
+
return 'unknown';
|
|
1449
|
+
}
|
|
1450
|
+
catch {
|
|
1451
|
+
return 'unknown';
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1385
1454
|
/**
|
|
1386
1455
|
* Checks if the project uses TypeScript (looks for tsconfig.json).
|
|
1387
1456
|
*/
|
|
@@ -1432,14 +1501,12 @@ function buildDynamicImportPath(outputTemplate, initDir) {
|
|
|
1432
1501
|
* Ensures that an i18n initialization file exists in the project.
|
|
1433
1502
|
* If no existing init file is found, generates a sensible default.
|
|
1434
1503
|
*
|
|
1435
|
-
*
|
|
1436
|
-
* i18next-resources-to-backend
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1439
|
-
* For React projects: creates i18n.ts with react-i18next integration.
|
|
1440
|
-
* For non-React projects: creates i18n.ts with basic i18next init.
|
|
1504
|
+
* The generated file's backend strategy depends on the project context:
|
|
1505
|
+
* - React app without i18next.t() → `i18next-resources-to-backend` (async dynamic imports)
|
|
1506
|
+
* - React app with i18next.t() → bundled resources (static imports, synchronous)
|
|
1507
|
+
* - Server-side (no React) → `i18next-fs-backend` (filesystem, initImmediate: false + preload)
|
|
1441
1508
|
*/
|
|
1442
|
-
async function ensureI18nInitFile(hasReact, hasTypeScript, config, logger) {
|
|
1509
|
+
async function ensureI18nInitFile(hasReact, hasTypeScript, config, logger, usesI18nextT) {
|
|
1443
1510
|
const cwd = process.cwd();
|
|
1444
1511
|
// Check for existing init files in common locations
|
|
1445
1512
|
const searchDirs = ['src', '.'];
|
|
@@ -1473,125 +1540,17 @@ async function ensureI18nInitFile(hasReact, hasTypeScript, config, logger) {
|
|
|
1473
1540
|
const initDir = srcExists ? join(cwd, 'src') : cwd;
|
|
1474
1541
|
const initFileExt = hasTypeScript ? '.ts' : '.js';
|
|
1475
1542
|
const initFilePath = join(initDir, 'i18n' + initFileExt);
|
|
1476
|
-
const
|
|
1477
|
-
const
|
|
1478
|
-
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
}
|
|
1487
|
-
const initBlock = initOptions.join(',\n');
|
|
1488
|
-
let initContent;
|
|
1489
|
-
if (typeof config.extract.output === 'string') {
|
|
1490
|
-
// Derive a dynamic import path so the generated init file loads translations automatically
|
|
1491
|
-
const dynamicImportPath = buildDynamicImportPath(config.extract.output, initDir);
|
|
1492
|
-
const importPathTemplate = dynamicImportPath
|
|
1493
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
1494
|
-
.replace(/\{\{language\}\}|\{\{lng\}\}/g, '${language}')
|
|
1495
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
1496
|
-
.replace(/\{\{namespace\}\}/g, '${namespace}');
|
|
1497
|
-
const hasNamespace = config.extract.output.includes('{{namespace}}');
|
|
1498
|
-
const callbackParams = hasNamespace ? hasTypeScript ? 'language: string, namespace: string' : 'language, namespace' : hasTypeScript ? 'language: string' : 'language';
|
|
1499
|
-
const backendUseLine = ' .use(resourcesToBackend((' + callbackParams + ') => import(`' + importPathTemplate + '`)))';
|
|
1500
|
-
if (hasReact) {
|
|
1501
|
-
initContent = `// Generated by i18next-cli — review and adapt to your project's needs.
|
|
1502
|
-
// You may need to install dependencies: npm install i18next react-i18next i18next-resources-to-backend
|
|
1503
|
-
//
|
|
1504
|
-
// Other translation loading approaches:
|
|
1505
|
-
// • Static imports or bundled JSON: https://www.i18next.com/how-to/add-or-load-translations
|
|
1506
|
-
// • Lazy-load from a server: https://github.com/i18next/i18next-http-backend
|
|
1507
|
-
// • Manage translations with your team via Locize: https://www.locize.com
|
|
1508
|
-
// (see i18next-locize-backend: https://github.com/locize/i18next-locize-backend)
|
|
1509
|
-
import i18next from 'i18next'
|
|
1510
|
-
import { initReactI18next } from 'react-i18next'
|
|
1511
|
-
import resourcesToBackend from 'i18next-resources-to-backend'
|
|
1512
|
-
|
|
1513
|
-
i18next
|
|
1514
|
-
.use(initReactI18next)
|
|
1515
|
-
${backendUseLine}
|
|
1516
|
-
.init({
|
|
1517
|
-
${initBlock}
|
|
1518
|
-
})
|
|
1519
|
-
|
|
1520
|
-
export default i18next
|
|
1521
|
-
`;
|
|
1522
|
-
}
|
|
1523
|
-
else {
|
|
1524
|
-
initContent = `// Generated by i18next-cli — review and adapt to your project's needs.
|
|
1525
|
-
// You may need to install the dependency: npm install i18next i18next-resources-to-backend
|
|
1526
|
-
//
|
|
1527
|
-
// Other translation loading approaches:
|
|
1528
|
-
// • Static imports or bundled JSON: https://www.i18next.com/how-to/add-or-load-translations
|
|
1529
|
-
// • Lazy-load from a server: https://github.com/i18next/i18next-http-backend
|
|
1530
|
-
// • Manage translations with your team via Locize: https://www.locize.com
|
|
1531
|
-
// (see i18next-locize-backend: https://github.com/locize/i18next-locize-backend)
|
|
1532
|
-
import i18next from 'i18next'
|
|
1533
|
-
import resourcesToBackend from 'i18next-resources-to-backend'
|
|
1534
|
-
|
|
1535
|
-
i18next
|
|
1536
|
-
${backendUseLine}
|
|
1537
|
-
.init({
|
|
1538
|
-
${initBlock}
|
|
1539
|
-
})
|
|
1540
|
-
|
|
1541
|
-
export default i18next
|
|
1542
|
-
`;
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
else {
|
|
1546
|
-
// Output is a function — can't derive import path, fall back to comments only
|
|
1547
|
-
if (hasReact) {
|
|
1548
|
-
initContent = `// Generated by i18next-cli — review and adapt to your project's needs.
|
|
1549
|
-
// You may need to install dependencies: npm install i18next react-i18next
|
|
1550
|
-
//
|
|
1551
|
-
// Loading translations:
|
|
1552
|
-
// • Static imports or bundled JSON: https://www.i18next.com/how-to/add-or-load-translations
|
|
1553
|
-
// • Lazy-load in memory with dynamic imports: https://github.com/i18next/i18next-resources-to-backend
|
|
1554
|
-
// • Lazy-load from a backend: https://github.com/i18next/i18next-http-backend
|
|
1555
|
-
// • Manage translations with your team via Locize: https://www.locize.com
|
|
1556
|
-
// (see i18next-locize-backend: https://github.com/locize/i18next-locize-backend)
|
|
1557
|
-
import i18next from 'i18next'
|
|
1558
|
-
import { initReactI18next } from 'react-i18next'
|
|
1559
|
-
|
|
1560
|
-
i18next
|
|
1561
|
-
.use(initReactI18next)
|
|
1562
|
-
.init({
|
|
1563
|
-
returnEmptyString: false, // allows empty string as valid translation
|
|
1564
|
-
// lng: ${config.locales.at(-1)}, // or add a language detector to detect the preferred language of your user
|
|
1565
|
-
fallbackLng: '${primaryLang}'
|
|
1566
|
-
// resources: { ... } — or use a backend plugin to load translations
|
|
1567
|
-
})
|
|
1568
|
-
|
|
1569
|
-
export default i18next
|
|
1570
|
-
`;
|
|
1571
|
-
}
|
|
1572
|
-
else {
|
|
1573
|
-
initContent = `// Generated by i18next-cli — review and adapt to your project's needs.
|
|
1574
|
-
// You may need to install the dependency: npm install i18next
|
|
1575
|
-
//
|
|
1576
|
-
// Loading translations:
|
|
1577
|
-
// • Static imports or bundled JSON: https://www.i18next.com/how-to/add-or-load-translations
|
|
1578
|
-
// • Lazy-load in memory with dynamic imports: https://github.com/i18next/i18next-resources-to-backend
|
|
1579
|
-
// • Lazy-load from a backend: https://github.com/i18next/i18next-http-backend
|
|
1580
|
-
// • Manage translations with your team via Locize: https://www.locize.com
|
|
1581
|
-
// (see i18next-locize-backend: https://github.com/locize/i18next-locize-backend)
|
|
1582
|
-
import i18next from 'i18next'
|
|
1583
|
-
|
|
1584
|
-
i18next.init({
|
|
1585
|
-
returnEmptyString: false, // allows empty string as valid translation
|
|
1586
|
-
// lng: ${config.locales.at(-1)}, // or add a language detector to detect the preferred language of your user
|
|
1587
|
-
fallbackLng: '${primaryLang}'
|
|
1588
|
-
// resources: { ... } — or use a backend plugin to load translations
|
|
1589
|
-
})
|
|
1590
|
-
|
|
1591
|
-
export default i18next
|
|
1592
|
-
`;
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1543
|
+
const environment = await detectProjectEnvironment();
|
|
1544
|
+
const strategy = determineBackendStrategy(environment, usesI18nextT);
|
|
1545
|
+
const outputTemplate = typeof config.extract.output === 'string' ? config.extract.output : null;
|
|
1546
|
+
const initContent = buildInitFileContent({
|
|
1547
|
+
strategy,
|
|
1548
|
+
hasReact,
|
|
1549
|
+
hasTypeScript,
|
|
1550
|
+
config,
|
|
1551
|
+
initDir,
|
|
1552
|
+
outputTemplate
|
|
1553
|
+
});
|
|
1595
1554
|
try {
|
|
1596
1555
|
await mkdir(initDir, { recursive: true });
|
|
1597
1556
|
await writeFile(initFilePath, initContent);
|
|
@@ -1603,6 +1562,171 @@ export default i18next
|
|
|
1603
1562
|
return null;
|
|
1604
1563
|
}
|
|
1605
1564
|
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Determines which backend strategy to use for the i18n init file.
|
|
1567
|
+
*
|
|
1568
|
+
* Decision logic:
|
|
1569
|
+
* 1. Node.js server with filesystem → `fs-backend`
|
|
1570
|
+
* (synchronous with `initImmediate: false` + `preload`)
|
|
1571
|
+
* 2. Browser / edge / unknown with `i18next.t()` outside React components →
|
|
1572
|
+
* `bundled-resources` (static imports so resources are available synchronously)
|
|
1573
|
+
* 3. Otherwise → `resources-to-backend` (async dynamic imports, lazy-loaded)
|
|
1574
|
+
*/
|
|
1575
|
+
function determineBackendStrategy(environment, usesI18nextT) {
|
|
1576
|
+
if (environment === 'node-server')
|
|
1577
|
+
return 'fs-backend';
|
|
1578
|
+
if (usesI18nextT)
|
|
1579
|
+
return 'bundled-resources';
|
|
1580
|
+
return 'resources-to-backend';
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Builds the full i18n init file content from composable parts,
|
|
1584
|
+
* avoiding repetition across different strategies.
|
|
1585
|
+
*/
|
|
1586
|
+
function buildInitFileContent(opts) {
|
|
1587
|
+
const { strategy, hasReact, hasTypeScript, config, initDir, outputTemplate } = opts;
|
|
1588
|
+
const primaryLang = config.extract.primaryLanguage ?? config.locales[0] ?? 'en';
|
|
1589
|
+
const defaultNS = config.extract.defaultNS !== false ? (config.extract.defaultNS || 'translation') : null;
|
|
1590
|
+
const ns = defaultNS || 'translation';
|
|
1591
|
+
// ── Dependencies for the install hint ──
|
|
1592
|
+
const deps = ['i18next'];
|
|
1593
|
+
if (hasReact)
|
|
1594
|
+
deps.push('react-i18next');
|
|
1595
|
+
if (strategy === 'resources-to-backend' && outputTemplate)
|
|
1596
|
+
deps.push('i18next-resources-to-backend');
|
|
1597
|
+
if (strategy === 'fs-backend' && outputTemplate)
|
|
1598
|
+
deps.push('i18next-fs-backend');
|
|
1599
|
+
const lines = [];
|
|
1600
|
+
// ── Header comment ──
|
|
1601
|
+
lines.push("// Generated by i18next-cli — review and adapt to your project's needs.", `// You may need to install dependencies: npm install ${deps.join(' ')}`, '//', '// Other translation loading approaches:', '// • Static imports or bundled JSON: https://www.i18next.com/how-to/add-or-load-translations', '// • Lazy-load from a server: https://github.com/i18next/i18next-http-backend', '// • Manage translations with your team via Locize: https://www.locize.com', '// (see i18next-locize-backend: https://github.com/locize/i18next-locize-backend)');
|
|
1602
|
+
// ── Import declarations ──
|
|
1603
|
+
lines.push("import i18next from 'i18next'");
|
|
1604
|
+
if (hasReact)
|
|
1605
|
+
lines.push("import { initReactI18next } from 'react-i18next'");
|
|
1606
|
+
if (outputTemplate) {
|
|
1607
|
+
switch (strategy) {
|
|
1608
|
+
case 'resources-to-backend':
|
|
1609
|
+
lines.push("import resourcesToBackend from 'i18next-resources-to-backend'");
|
|
1610
|
+
break;
|
|
1611
|
+
case 'bundled-resources':
|
|
1612
|
+
for (const locale of config.locales) {
|
|
1613
|
+
const importPath = buildResourceImportPath(outputTemplate, initDir, locale, ns);
|
|
1614
|
+
lines.push(`import ${toResourceVarName(locale, ns)} from '${importPath}'`);
|
|
1615
|
+
}
|
|
1616
|
+
break;
|
|
1617
|
+
case 'fs-backend':
|
|
1618
|
+
lines.push("import Backend from 'i18next-fs-backend'");
|
|
1619
|
+
lines.push("import { resolve, dirname } from 'node:path'");
|
|
1620
|
+
lines.push("import { fileURLToPath } from 'node:url'");
|
|
1621
|
+
break;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
// ── Pre-init statements ──
|
|
1625
|
+
lines.push('');
|
|
1626
|
+
if (strategy === 'fs-backend' && outputTemplate) {
|
|
1627
|
+
lines.push('const __dirname = dirname(fileURLToPath(import.meta.url))');
|
|
1628
|
+
lines.push('');
|
|
1629
|
+
}
|
|
1630
|
+
// ── .use() chain entries ──
|
|
1631
|
+
const useEntries = [];
|
|
1632
|
+
if (hasReact)
|
|
1633
|
+
useEntries.push(' .use(initReactI18next)');
|
|
1634
|
+
if (outputTemplate) {
|
|
1635
|
+
if (strategy === 'resources-to-backend') {
|
|
1636
|
+
const dynamicPath = buildDynamicImportPath(outputTemplate, initDir);
|
|
1637
|
+
const importPathTemplate = dynamicPath
|
|
1638
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
1639
|
+
.replace(/\{\{language\}\}|\{\{lng\}\}/g, '${language}')
|
|
1640
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
1641
|
+
.replace(/\{\{namespace\}\}/g, '${namespace}');
|
|
1642
|
+
const hasNamespace = outputTemplate.includes('{{namespace}}');
|
|
1643
|
+
const cbParams = hasNamespace
|
|
1644
|
+
? (hasTypeScript ? 'language: string, namespace: string' : 'language, namespace')
|
|
1645
|
+
: (hasTypeScript ? 'language: string' : 'language');
|
|
1646
|
+
useEntries.push(` .use(resourcesToBackend((${cbParams}) => import(\`${importPathTemplate}\`)))`);
|
|
1647
|
+
}
|
|
1648
|
+
else if (strategy === 'fs-backend') {
|
|
1649
|
+
useEntries.push(' .use(Backend)');
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
// Emit the i18next chain — use compact form if no .use() calls
|
|
1653
|
+
const awaitPrefix = (strategy === 'fs-backend' && outputTemplate) ? 'await ' : '';
|
|
1654
|
+
if (useEntries.length > 0) {
|
|
1655
|
+
lines.push(`${awaitPrefix}i18next`);
|
|
1656
|
+
lines.push(...useEntries);
|
|
1657
|
+
lines.push(' .init({');
|
|
1658
|
+
}
|
|
1659
|
+
else {
|
|
1660
|
+
lines.push(`${awaitPrefix}i18next.init({`);
|
|
1661
|
+
}
|
|
1662
|
+
// ── .init() options ──
|
|
1663
|
+
const initOpts = [];
|
|
1664
|
+
if (strategy === 'fs-backend' && outputTemplate) {
|
|
1665
|
+
initOpts.push(' initImmediate: false,');
|
|
1666
|
+
}
|
|
1667
|
+
initOpts.push(' returnEmptyString: false, // allows empty string as valid translation');
|
|
1668
|
+
initOpts.push(` // lng: '${config.locales.at(-1)}', // or add a language detector to detect the preferred language of your user`);
|
|
1669
|
+
initOpts.push(` fallbackLng: '${primaryLang}',`);
|
|
1670
|
+
if (defaultNS) {
|
|
1671
|
+
initOpts.push(` defaultNS: '${ns}',`);
|
|
1672
|
+
}
|
|
1673
|
+
// Strategy-specific init options
|
|
1674
|
+
if (outputTemplate) {
|
|
1675
|
+
if (strategy === 'bundled-resources') {
|
|
1676
|
+
initOpts.push(' resources: {');
|
|
1677
|
+
for (const locale of config.locales) {
|
|
1678
|
+
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(locale) ? locale : `'${locale}'`;
|
|
1679
|
+
initOpts.push(` ${key}: { ${ns}: ${toResourceVarName(locale, ns)} },`);
|
|
1680
|
+
}
|
|
1681
|
+
initOpts.push(' },');
|
|
1682
|
+
}
|
|
1683
|
+
else if (strategy === 'fs-backend') {
|
|
1684
|
+
const loadPath = buildFsBackendLoadPath(outputTemplate, initDir);
|
|
1685
|
+
initOpts.push(` preload: [${config.locales.map(l => `'${l}'`).join(', ')}],`);
|
|
1686
|
+
initOpts.push(' backend: {');
|
|
1687
|
+
initOpts.push(` loadPath: resolve(__dirname, '${loadPath}'),`);
|
|
1688
|
+
initOpts.push(' },');
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
else {
|
|
1692
|
+
// No concrete output path — user needs to configure loading manually
|
|
1693
|
+
initOpts.push(' // resources: { ... } — or use a backend plugin to load translations');
|
|
1694
|
+
}
|
|
1695
|
+
lines.push(initOpts.join('\n'));
|
|
1696
|
+
lines.push(' })');
|
|
1697
|
+
lines.push('');
|
|
1698
|
+
lines.push('export default i18next');
|
|
1699
|
+
lines.push('');
|
|
1700
|
+
return lines.join('\n');
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Resolves the import path for a specific locale/namespace resource file
|
|
1704
|
+
* (used by the bundled-resources strategy).
|
|
1705
|
+
*/
|
|
1706
|
+
function buildResourceImportPath(outputTemplate, initDir, locale, namespace) {
|
|
1707
|
+
const rel = buildDynamicImportPath(outputTemplate, initDir);
|
|
1708
|
+
return rel
|
|
1709
|
+
.replace(/\{\{language\}\}|\{\{lng\}\}/g, locale)
|
|
1710
|
+
.replace(/\{\{namespace\}\}|\{\{ns\}\}/g, namespace);
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Resolves the loadPath for i18next-fs-backend, using i18next's `{{lng}}`
|
|
1714
|
+
* and `{{ns}}` interpolation syntax.
|
|
1715
|
+
*/
|
|
1716
|
+
function buildFsBackendLoadPath(outputTemplate, initDir) {
|
|
1717
|
+
const rel = buildDynamicImportPath(outputTemplate, initDir);
|
|
1718
|
+
return rel
|
|
1719
|
+
.replace(/\{\{language\}\}/g, '{{lng}}')
|
|
1720
|
+
.replace(/\{\{namespace\}\}/g, '{{ns}}');
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Converts a locale + namespace pair to a valid JS variable name.
|
|
1724
|
+
* E.g. ('en', 'translation') → 'enTranslation', ('zh-CN', 'common') → 'zhCNCommon'
|
|
1725
|
+
*/
|
|
1726
|
+
function toResourceVarName(locale, namespace) {
|
|
1727
|
+
const sanitizedLocale = locale.replace(/[^a-zA-Z0-9]/g, '');
|
|
1728
|
+
return sanitizedLocale + namespace.charAt(0).toUpperCase() + namespace.slice(1);
|
|
1729
|
+
}
|
|
1606
1730
|
/**
|
|
1607
1731
|
* Common entry-point file names, checked in priority order.
|
|
1608
1732
|
*/
|
package/dist/esm/locize.js
CHANGED
|
@@ -5,29 +5,37 @@ import inquirer from 'inquirer';
|
|
|
5
5
|
import { sep, resolve } from 'node:path';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Resolves the locize-cli executable to use.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Tries, in order:
|
|
11
|
+
* 1. A locally / globally installed `locize` binary
|
|
12
|
+
* 2. Falls back to `npx locize-cli` so it can be fetched on demand
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* ```
|
|
14
|
+
* If neither works the process exits with an error.
|
|
15
|
+
*
|
|
16
|
+
* @returns An object with `cmd` (the executable) and `prefixArgs` (extra args
|
|
17
|
+
* to prepend before the locize sub-command, e.g. `['locize-cli']`
|
|
18
|
+
* when running through npx).
|
|
18
19
|
*/
|
|
19
|
-
async function
|
|
20
|
+
async function resolveLocizeBin() {
|
|
21
|
+
// 1. Try a locally / globally installed binary
|
|
20
22
|
try {
|
|
21
23
|
await execa('locize', ['--version']);
|
|
24
|
+
return { cmd: 'locize', prefixArgs: [] };
|
|
22
25
|
}
|
|
23
|
-
catch
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
catch {
|
|
27
|
+
// not found – continue
|
|
28
|
+
}
|
|
29
|
+
// 2. Fall back to npx
|
|
30
|
+
try {
|
|
31
|
+
console.log(styleText('yellow', '`locize` command not found – trying npx...'));
|
|
32
|
+
await execa('npx', ['locize-cli', '--version']);
|
|
33
|
+
return { cmd: 'npx', prefixArgs: ['locize-cli'] };
|
|
30
34
|
}
|
|
35
|
+
catch {
|
|
36
|
+
// npx also failed
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
33
41
|
* Interactive setup wizard for configuring Locize credentials.
|
|
@@ -234,14 +242,23 @@ function buildArgs(command, config, cliOptions) {
|
|
|
234
242
|
* ```
|
|
235
243
|
*/
|
|
236
244
|
async function runLocizeCommand(command, config, cliOptions = {}) {
|
|
237
|
-
await
|
|
245
|
+
const resolved = await resolveLocizeBin();
|
|
246
|
+
if (!resolved) {
|
|
247
|
+
console.error(styleText('red', 'Error: `locize-cli` command not found.'));
|
|
248
|
+
console.log(styleText('yellow', 'Please install it to use the Locize integration:'));
|
|
249
|
+
console.log(styleText('cyan', ' npm install -g locize-cli'));
|
|
250
|
+
console.log(styleText('yellow', 'Or make sure npx is available so it can be fetched on demand.'));
|
|
251
|
+
process.exit(1);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const { cmd, prefixArgs } = resolved;
|
|
238
255
|
const spinner = ora(`Running 'locize ${command}'...\n`).start();
|
|
239
256
|
let effectiveConfig = config;
|
|
240
257
|
try {
|
|
241
258
|
// 1. First attempt
|
|
242
|
-
const initialArgs = buildArgs(command, effectiveConfig, cliOptions);
|
|
243
|
-
console.log(styleText('cyan', `\nRunning 'locize ${maskArgs(initialArgs).join(' ')}'...`));
|
|
244
|
-
const result = await execa(
|
|
259
|
+
const initialArgs = [...prefixArgs, ...buildArgs(command, effectiveConfig, cliOptions)];
|
|
260
|
+
console.log(styleText('cyan', `\nRunning 'locize ${maskArgs(initialArgs.slice(prefixArgs.length)).join(' ')}'...`));
|
|
261
|
+
const result = await execa(cmd, initialArgs, { stdio: 'pipe' });
|
|
245
262
|
spinner.succeed(styleText('green', `'locize ${command}' completed successfully.`));
|
|
246
263
|
if (result?.stdout)
|
|
247
264
|
console.log(result.stdout); // Print captured output on success
|
|
@@ -257,9 +274,9 @@ async function runLocizeCommand(command, config, cliOptions = {}) {
|
|
|
257
274
|
spinner.start('Retrying with new credentials...');
|
|
258
275
|
try {
|
|
259
276
|
// 3. Retry attempt, rebuilding args with the NOW-UPDATED currentConfig object
|
|
260
|
-
const retryArgs = buildArgs(command, effectiveConfig, cliOptions);
|
|
261
|
-
console.log(styleText('cyan', `\nRunning 'locize ${maskArgs(retryArgs).join(' ')}'...`));
|
|
262
|
-
const result = await execa(
|
|
277
|
+
const retryArgs = [...prefixArgs, ...buildArgs(command, effectiveConfig, cliOptions)];
|
|
278
|
+
console.log(styleText('cyan', `\nRunning 'locize ${maskArgs(retryArgs.slice(prefixArgs.length)).join(' ')}'...`));
|
|
279
|
+
const result = await execa(cmd, retryArgs, { stdio: 'pipe' });
|
|
263
280
|
spinner.succeed(styleText('green', 'Retry successful!'));
|
|
264
281
|
if (result?.stdout)
|
|
265
282
|
console.log(result.stdout);
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumenter.d.ts","sourceRoot":"","sources":["../../../src/instrumenter/core/instrumenter.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,eAAe,EAA6B,sBAAsB,EAAiE,MAAM,aAAa,CAAA;AAUvN;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,mBAAmB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,sBAAsB,CAAC,
|
|
1
|
+
{"version":3,"file":"instrumenter.d.ts","sourceRoot":"","sources":["../../../src/instrumenter/core/instrumenter.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,eAAe,EAA6B,sBAAsB,EAAiE,MAAM,aAAa,CAAA;AAUvN;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,mBAAmB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,sBAAsB,CAAC,CAoNjC;AA4wDD;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,eAAe,EAAE,EAC7B,MAAM,EAAE,oBAAoB,EAC5B,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CAoDf"}
|
package/types/locize.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locize.d.ts","sourceRoot":"","sources":["../src/locize.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"locize.d.ts","sourceRoot":"","sources":["../src/locize.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAgTnD,eAAO,MAAM,aAAa,GAAI,QAAQ,oBAAoB,EAAE,aAAa,GAAG,kBAAiD,CAAA;AAC7H,eAAO,MAAM,iBAAiB,GAAI,QAAQ,oBAAoB,EAAE,aAAa,GAAG,kBAAqD,CAAA;AACrI,eAAO,MAAM,gBAAgB,GAAI,QAAQ,oBAAoB,EAAE,aAAa,GAAG,kBAAoD,CAAA"}
|