i18next-cli 1.56.4 → 1.56.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/core/translation-manager.js +23 -0
- package/dist/cjs/extractor/parsers/call-expression-handler.js +42 -1
- package/dist/cjs/extractor/parsers/jsx-handler.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/core/translation-manager.js +23 -0
- package/dist/esm/extractor/parsers/call-expression-handler.js +42 -1
- package/dist/esm/extractor/parsers/jsx-handler.js +1 -1
- package/package.json +1 -1
- package/types/extractor/core/translation-manager.d.ts.map +1 -1
- package/types/extractor/parsers/call-expression-handler.d.ts +10 -0
- package/types/extractor/parsers/call-expression-handler.d.ts.map +1 -1
package/dist/cjs/cli.js
CHANGED
|
@@ -32,7 +32,7 @@ const program = new commander.Command();
|
|
|
32
32
|
program
|
|
33
33
|
.name('i18next-cli')
|
|
34
34
|
.description('A unified, high-performance i18next CLI.')
|
|
35
|
-
.version('1.56.
|
|
35
|
+
.version('1.56.6'); // This string is replaced with the actual version at build time by rollup
|
|
36
36
|
// new: global config override option
|
|
37
37
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
38
38
|
program
|
|
@@ -224,6 +224,11 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
|
|
|
224
224
|
const cardinalCategories = cardinalRules.resolvedOptions().pluralCategories;
|
|
225
225
|
cardinalCategories.forEach(cat => targetLanguagePluralCategories.add(cat));
|
|
226
226
|
ordinalRules.resolvedOptions().pluralCategories.forEach(cat => targetLanguagePluralCategories.add(`ordinal_${cat}`));
|
|
227
|
+
// Plural categories of the primary language — used to recognise locale-specific
|
|
228
|
+
// plural variants (e.g. French `_many` when primary is English) so we don't
|
|
229
|
+
// treat their absence from the primary file as a "divergence" during --sync-all.
|
|
230
|
+
const primaryCardinalCategoriesSet = new Set(pluralRules.safePluralRules(primaryLanguage, { type: 'cardinal' }).resolvedOptions().pluralCategories);
|
|
231
|
+
const primaryOrdinalCategoriesSet = new Set(pluralRules.safePluralRules(primaryLanguage, { type: 'ordinal' }).resolvedOptions().pluralCategories);
|
|
227
232
|
// When allPluralForms is enabled, compute the union of cardinal categories across all configured locales.
|
|
228
233
|
// This ensures every locale gets the same set of plural keys — but only the forms actually needed by at least one locale.
|
|
229
234
|
const allLocalesCardinalCategories = config.extract.allPluralForms
|
|
@@ -762,8 +767,26 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
|
|
|
762
767
|
else {
|
|
763
768
|
// Non-primary locale behavior
|
|
764
769
|
const isVariantKey = key.includes(pluralSeparator) || key.includes(contextSeparator);
|
|
770
|
+
// A plural variant whose category exists in the current locale but not in the
|
|
771
|
+
// primary language (e.g. French `_many` vs English `one`/`other`) will always be
|
|
772
|
+
// absent from the primary file by CLDR design. Treat that absence as expected —
|
|
773
|
+
// not as the primary "diverging" from the default — so --sync-all preserves the
|
|
774
|
+
// locale-specific translation instead of clearing it on every run. (issue #248)
|
|
775
|
+
const isLocaleSpecificPluralVariant = (() => {
|
|
776
|
+
if (!hasCount)
|
|
777
|
+
return false;
|
|
778
|
+
const parts = String(key).split(pluralSeparator);
|
|
779
|
+
if (parts.length < 2)
|
|
780
|
+
return false;
|
|
781
|
+
const lastPart = parts[parts.length - 1];
|
|
782
|
+
if (isOrdinal && parts.length >= 3 && parts[parts.length - 2] === 'ordinal') {
|
|
783
|
+
return !primaryOrdinalCategoriesSet.has(lastPart);
|
|
784
|
+
}
|
|
785
|
+
return !primaryCardinalCategoriesSet.has(lastPart);
|
|
786
|
+
})();
|
|
765
787
|
const primaryDivergedFromDefault = Boolean(defaultValue$1 &&
|
|
766
788
|
!primaryShouldPreserveObject &&
|
|
789
|
+
!isLocaleSpecificPluralVariant &&
|
|
767
790
|
(primaryExistingValue === undefined ||
|
|
768
791
|
primaryIsStaleObject ||
|
|
769
792
|
((!isVariantKey || explicitDefault) &&
|
|
@@ -608,7 +608,48 @@ class CallExpressionHandler {
|
|
|
608
608
|
return [];
|
|
609
609
|
}
|
|
610
610
|
}
|
|
611
|
-
|
|
611
|
+
return this.extractKeysFromSelectorExpression(body);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Recursively extracts key paths from a selector-body expression.
|
|
615
|
+
*
|
|
616
|
+
* On top of the straight MemberExpression walk this also descends into
|
|
617
|
+
* branching forms so keys referenced in either branch are preserved
|
|
618
|
+
* (see #247): ternaries (`cond ? $.a : $.b`), nullish/logical
|
|
619
|
+
* short-circuits (`x ?? $.a`, `x && $.a`, `x || $.a`), and common
|
|
620
|
+
* wrapper nodes (parentheses, TS type assertions).
|
|
621
|
+
*/
|
|
622
|
+
extractKeysFromSelectorExpression(expr) {
|
|
623
|
+
if (!expr)
|
|
624
|
+
return [];
|
|
625
|
+
// Unwrap wrappers that don't change the expressed key path.
|
|
626
|
+
if (expr.type === 'ParenthesisExpression' ||
|
|
627
|
+
expr.type === 'TsAsExpression' ||
|
|
628
|
+
expr.type === 'TsSatisfiesExpression' ||
|
|
629
|
+
expr.type === 'TsNonNullExpression' ||
|
|
630
|
+
expr.type === 'TsConstAssertion') {
|
|
631
|
+
return this.extractKeysFromSelectorExpression(expr.expression);
|
|
632
|
+
}
|
|
633
|
+
// Ternary: union of both branches.
|
|
634
|
+
if (expr.type === 'ConditionalExpression') {
|
|
635
|
+
return [
|
|
636
|
+
...this.extractKeysFromSelectorExpression(expr.consequent),
|
|
637
|
+
...this.extractKeysFromSelectorExpression(expr.alternate),
|
|
638
|
+
];
|
|
639
|
+
}
|
|
640
|
+
// Short-circuit logicals (`||`, `&&`, `??`): union of both sides. SWC
|
|
641
|
+
// may emit these as LogicalExpression or BinaryExpression depending on
|
|
642
|
+
// version, so accept both.
|
|
643
|
+
if ((expr.type === 'LogicalExpression' || expr.type === 'BinaryExpression') &&
|
|
644
|
+
(expr.operator === '||' || expr.operator === '&&' || expr.operator === '??')) {
|
|
645
|
+
return [
|
|
646
|
+
...this.extractKeysFromSelectorExpression(expr.left),
|
|
647
|
+
...this.extractKeysFromSelectorExpression(expr.right),
|
|
648
|
+
];
|
|
649
|
+
}
|
|
650
|
+
if (expr.type !== 'MemberExpression')
|
|
651
|
+
return [];
|
|
652
|
+
let current = expr;
|
|
612
653
|
// Each element is an array of possible values for that position in the key path
|
|
613
654
|
const parts = [];
|
|
614
655
|
// Recursively walk down MemberExpressions
|
|
@@ -73,7 +73,7 @@ class JSXHandler {
|
|
|
73
73
|
const where = loc
|
|
74
74
|
? `${this.getCurrentFile()}:${loc.line}:${loc.column}`
|
|
75
75
|
: this.getCurrentFile();
|
|
76
|
-
emit(`Error: <${elementName}> child {${name}} at ${where} won't match at runtime — react-i18next inlines the value (e.g. "<1>meow</1>"), but extraction produces "<1>{{${name}}}</1>". Use {{${name}}} (double braces) with values={{ ${name} }} for interpolation
|
|
76
|
+
emit(`Error: <${elementName}> child {${name}} at ${where} won't match at runtime — react-i18next inlines the value (e.g. "<1>meow</1>"), but extraction produces "<1>{{${name}}}</1>". Use {{${name}}} (double braces) with values={{ ${name} }} for interpolation.`);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
/**
|
package/dist/esm/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ const program = new Command();
|
|
|
30
30
|
program
|
|
31
31
|
.name('i18next-cli')
|
|
32
32
|
.description('A unified, high-performance i18next CLI.')
|
|
33
|
-
.version('1.56.
|
|
33
|
+
.version('1.56.6'); // This string is replaced with the actual version at build time by rollup
|
|
34
34
|
// new: global config override option
|
|
35
35
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
36
36
|
program
|
|
@@ -222,6 +222,11 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
|
|
|
222
222
|
const cardinalCategories = cardinalRules.resolvedOptions().pluralCategories;
|
|
223
223
|
cardinalCategories.forEach(cat => targetLanguagePluralCategories.add(cat));
|
|
224
224
|
ordinalRules.resolvedOptions().pluralCategories.forEach(cat => targetLanguagePluralCategories.add(`ordinal_${cat}`));
|
|
225
|
+
// Plural categories of the primary language — used to recognise locale-specific
|
|
226
|
+
// plural variants (e.g. French `_many` when primary is English) so we don't
|
|
227
|
+
// treat their absence from the primary file as a "divergence" during --sync-all.
|
|
228
|
+
const primaryCardinalCategoriesSet = new Set(safePluralRules(primaryLanguage, { type: 'cardinal' }).resolvedOptions().pluralCategories);
|
|
229
|
+
const primaryOrdinalCategoriesSet = new Set(safePluralRules(primaryLanguage, { type: 'ordinal' }).resolvedOptions().pluralCategories);
|
|
225
230
|
// When allPluralForms is enabled, compute the union of cardinal categories across all configured locales.
|
|
226
231
|
// This ensures every locale gets the same set of plural keys — but only the forms actually needed by at least one locale.
|
|
227
232
|
const allLocalesCardinalCategories = config.extract.allPluralForms
|
|
@@ -760,8 +765,26 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
|
|
|
760
765
|
else {
|
|
761
766
|
// Non-primary locale behavior
|
|
762
767
|
const isVariantKey = key.includes(pluralSeparator) || key.includes(contextSeparator);
|
|
768
|
+
// A plural variant whose category exists in the current locale but not in the
|
|
769
|
+
// primary language (e.g. French `_many` vs English `one`/`other`) will always be
|
|
770
|
+
// absent from the primary file by CLDR design. Treat that absence as expected —
|
|
771
|
+
// not as the primary "diverging" from the default — so --sync-all preserves the
|
|
772
|
+
// locale-specific translation instead of clearing it on every run. (issue #248)
|
|
773
|
+
const isLocaleSpecificPluralVariant = (() => {
|
|
774
|
+
if (!hasCount)
|
|
775
|
+
return false;
|
|
776
|
+
const parts = String(key).split(pluralSeparator);
|
|
777
|
+
if (parts.length < 2)
|
|
778
|
+
return false;
|
|
779
|
+
const lastPart = parts[parts.length - 1];
|
|
780
|
+
if (isOrdinal && parts.length >= 3 && parts[parts.length - 2] === 'ordinal') {
|
|
781
|
+
return !primaryOrdinalCategoriesSet.has(lastPart);
|
|
782
|
+
}
|
|
783
|
+
return !primaryCardinalCategoriesSet.has(lastPart);
|
|
784
|
+
})();
|
|
763
785
|
const primaryDivergedFromDefault = Boolean(defaultValue &&
|
|
764
786
|
!primaryShouldPreserveObject &&
|
|
787
|
+
!isLocaleSpecificPluralVariant &&
|
|
765
788
|
(primaryExistingValue === undefined ||
|
|
766
789
|
primaryIsStaleObject ||
|
|
767
790
|
((!isVariantKey || explicitDefault) &&
|
|
@@ -606,7 +606,48 @@ class CallExpressionHandler {
|
|
|
606
606
|
return [];
|
|
607
607
|
}
|
|
608
608
|
}
|
|
609
|
-
|
|
609
|
+
return this.extractKeysFromSelectorExpression(body);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Recursively extracts key paths from a selector-body expression.
|
|
613
|
+
*
|
|
614
|
+
* On top of the straight MemberExpression walk this also descends into
|
|
615
|
+
* branching forms so keys referenced in either branch are preserved
|
|
616
|
+
* (see #247): ternaries (`cond ? $.a : $.b`), nullish/logical
|
|
617
|
+
* short-circuits (`x ?? $.a`, `x && $.a`, `x || $.a`), and common
|
|
618
|
+
* wrapper nodes (parentheses, TS type assertions).
|
|
619
|
+
*/
|
|
620
|
+
extractKeysFromSelectorExpression(expr) {
|
|
621
|
+
if (!expr)
|
|
622
|
+
return [];
|
|
623
|
+
// Unwrap wrappers that don't change the expressed key path.
|
|
624
|
+
if (expr.type === 'ParenthesisExpression' ||
|
|
625
|
+
expr.type === 'TsAsExpression' ||
|
|
626
|
+
expr.type === 'TsSatisfiesExpression' ||
|
|
627
|
+
expr.type === 'TsNonNullExpression' ||
|
|
628
|
+
expr.type === 'TsConstAssertion') {
|
|
629
|
+
return this.extractKeysFromSelectorExpression(expr.expression);
|
|
630
|
+
}
|
|
631
|
+
// Ternary: union of both branches.
|
|
632
|
+
if (expr.type === 'ConditionalExpression') {
|
|
633
|
+
return [
|
|
634
|
+
...this.extractKeysFromSelectorExpression(expr.consequent),
|
|
635
|
+
...this.extractKeysFromSelectorExpression(expr.alternate),
|
|
636
|
+
];
|
|
637
|
+
}
|
|
638
|
+
// Short-circuit logicals (`||`, `&&`, `??`): union of both sides. SWC
|
|
639
|
+
// may emit these as LogicalExpression or BinaryExpression depending on
|
|
640
|
+
// version, so accept both.
|
|
641
|
+
if ((expr.type === 'LogicalExpression' || expr.type === 'BinaryExpression') &&
|
|
642
|
+
(expr.operator === '||' || expr.operator === '&&' || expr.operator === '??')) {
|
|
643
|
+
return [
|
|
644
|
+
...this.extractKeysFromSelectorExpression(expr.left),
|
|
645
|
+
...this.extractKeysFromSelectorExpression(expr.right),
|
|
646
|
+
];
|
|
647
|
+
}
|
|
648
|
+
if (expr.type !== 'MemberExpression')
|
|
649
|
+
return [];
|
|
650
|
+
let current = expr;
|
|
610
651
|
// Each element is an array of possible values for that position in the key path
|
|
611
652
|
const parts = [];
|
|
612
653
|
// Recursively walk down MemberExpressions
|
|
@@ -71,7 +71,7 @@ class JSXHandler {
|
|
|
71
71
|
const where = loc
|
|
72
72
|
? `${this.getCurrentFile()}:${loc.line}:${loc.column}`
|
|
73
73
|
: this.getCurrentFile();
|
|
74
|
-
emit(`Error: <${elementName}> child {${name}} at ${where} won't match at runtime — react-i18next inlines the value (e.g. "<1>meow</1>"), but extraction produces "<1>{{${name}}}</1>". Use {{${name}}} (double braces) with values={{ ${name} }} for interpolation
|
|
74
|
+
emit(`Error: <${elementName}> child {${name}} at ${where} won't match at runtime — react-i18next inlines the value (e.g. "<1>meow</1>"), but extraction produces "<1>{{${name}}}</1>". Use {{${name}}} (double braces) with values={{ ${name} }} for interpolation.`);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
/**
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AA8iC9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,EAC5B,EACE,uBAA+B,EAC/B,OAAe,EACf,oBAA4B,EAC5B,MAA4B,EAC7B,GAAE;IACD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAA;CACX,GACL,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAiK9B"}
|
|
@@ -67,6 +67,16 @@ export declare class CallExpressionHandler {
|
|
|
67
67
|
* @returns Extracted key paths, or empty array if not statically analyzable
|
|
68
68
|
*/
|
|
69
69
|
private extractKeysFromSelector;
|
|
70
|
+
/**
|
|
71
|
+
* Recursively extracts key paths from a selector-body expression.
|
|
72
|
+
*
|
|
73
|
+
* On top of the straight MemberExpression walk this also descends into
|
|
74
|
+
* branching forms so keys referenced in either branch are preserved
|
|
75
|
+
* (see #247): ternaries (`cond ? $.a : $.b`), nullish/logical
|
|
76
|
+
* short-circuits (`x ?? $.a`, `x && $.a`, `x || $.a`), and common
|
|
77
|
+
* wrapper nodes (parentheses, TS type assertions).
|
|
78
|
+
*/
|
|
79
|
+
private extractKeysFromSelectorExpression;
|
|
70
80
|
/**
|
|
71
81
|
* Generates plural form keys based on the primary language's plural rules.
|
|
72
82
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAa7D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,kBAAkB,CAAoB;IACvC,UAAU,cAAoB;IACrC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,iBAAiB,CAAsC;gBAG7D,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM,EAC5B,iBAAiB,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAA2B;IAW3E;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI;IA0ZxG;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,wBAAwB;IAyEhC;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,uBAAuB;
|
|
1
|
+
{"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAa7D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,kBAAkB,CAAoB;IACvC,UAAU,cAAoB;IACrC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,iBAAiB,CAAsC;gBAG7D,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM,EAC5B,iBAAiB,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAA2B;IAW3E;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI;IA0ZxG;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,wBAAwB;IAyEhC;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,uBAAuB;IAgB/B;;;;;;;;OAQG;IACH,OAAO,CAAC,iCAAiC;IAwFzC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,gBAAgB;IAyMxB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
|