@katerynakhar/i18n-keeper 0.16.0
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/LICENSE +21 -0
- package/README.md +666 -0
- package/dist/apply.d.ts +66 -0
- package/dist/apply.js +254 -0
- package/dist/apply.js.map +1 -0
- package/dist/check.d.ts +5 -0
- package/dist/check.js +326 -0
- package/dist/check.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +525 -0
- package/dist/cli.js.map +1 -0
- package/dist/formats/error.d.ts +12 -0
- package/dist/formats/error.js +19 -0
- package/dist/formats/error.js.map +1 -0
- package/dist/formats/flatten.d.ts +6 -0
- package/dist/formats/flatten.js +26 -0
- package/dist/formats/flatten.js.map +1 -0
- package/dist/formats/json-write.d.ts +7 -0
- package/dist/formats/json-write.js +55 -0
- package/dist/formats/json-write.js.map +1 -0
- package/dist/formats/json.d.ts +6 -0
- package/dist/formats/json.js +21 -0
- package/dist/formats/json.js.map +1 -0
- package/dist/formats/php-write.d.ts +16 -0
- package/dist/formats/php-write.js +130 -0
- package/dist/formats/php-write.js.map +1 -0
- package/dist/formats/php.d.ts +44 -0
- package/dist/formats/php.js +356 -0
- package/dist/formats/php.js.map +1 -0
- package/dist/formats/po-write.d.ts +3 -0
- package/dist/formats/po-write.js +200 -0
- package/dist/formats/po-write.js.map +1 -0
- package/dist/formats/po.d.ts +30 -0
- package/dist/formats/po.js +202 -0
- package/dist/formats/po.js.map +1 -0
- package/dist/formats/write.d.ts +29 -0
- package/dist/formats/write.js +19 -0
- package/dist/formats/write.js.map +1 -0
- package/dist/formats/yaml-write.d.ts +12 -0
- package/dist/formats/yaml-write.js +42 -0
- package/dist/formats/yaml-write.js.map +1 -0
- package/dist/formats/yaml.d.ts +18 -0
- package/dist/formats/yaml.js +51 -0
- package/dist/formats/yaml.js.map +1 -0
- package/dist/glossary.d.ts +38 -0
- package/dist/glossary.js +140 -0
- package/dist/glossary.js.map +1 -0
- package/dist/lengths.d.ts +37 -0
- package/dist/lengths.js +195 -0
- package/dist/lengths.js.map +1 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +374 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory.d.ts +72 -0
- package/dist/memory.js +162 -0
- package/dist/memory.js.map +1 -0
- package/dist/placeholders.d.ts +15 -0
- package/dist/placeholders.js +103 -0
- package/dist/placeholders.js.map +1 -0
- package/dist/plurals.d.ts +59 -0
- package/dist/plurals.js +280 -0
- package/dist/plurals.js.map +1 -0
- package/dist/report.d.ts +2 -0
- package/dist/report.js +126 -0
- package/dist/report.js.map +1 -0
- package/dist/scan.d.ts +23 -0
- package/dist/scan.js +250 -0
- package/dist/scan.js.map +1 -0
- package/dist/translate.d.ts +93 -0
- package/dist/translate.js +369 -0
- package/dist/translate.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.js +50 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +28 -0
- package/dist/version.js.map +1 -0
- package/package.json +77 -0
- package/src/apply.ts +334 -0
- package/src/check.ts +468 -0
- package/src/cli.ts +637 -0
- package/src/formats/error.ts +19 -0
- package/src/formats/flatten.ts +38 -0
- package/src/formats/json-write.ts +66 -0
- package/src/formats/json.ts +27 -0
- package/src/formats/php-write.ts +146 -0
- package/src/formats/php.ts +414 -0
- package/src/formats/po-write.ts +230 -0
- package/src/formats/po.ts +233 -0
- package/src/formats/write.ts +41 -0
- package/src/formats/yaml-write.ts +53 -0
- package/src/formats/yaml.ts +62 -0
- package/src/glossary.ts +191 -0
- package/src/lengths.ts +214 -0
- package/src/mcp.ts +451 -0
- package/src/memory.ts +227 -0
- package/src/placeholders.ts +123 -0
- package/src/plurals.ts +320 -0
- package/src/report.ts +162 -0
- package/src/scan.ts +280 -0
- package/src/translate.ts +486 -0
- package/src/types.ts +136 -0
- package/src/version.ts +27 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function kindOf(value) {
|
|
2
|
+
if (value === null)
|
|
3
|
+
return 'null';
|
|
4
|
+
if (typeof value === 'number')
|
|
5
|
+
return 'number';
|
|
6
|
+
if (typeof value === 'boolean')
|
|
7
|
+
return 'boolean';
|
|
8
|
+
return 'string';
|
|
9
|
+
}
|
|
10
|
+
/** Flattens a parsed locale value into dot-notation keys, shared by every format. */
|
|
11
|
+
export function flattenValue(node, prefix, file, leaves, containers) {
|
|
12
|
+
if (node !== null && typeof node === 'object') {
|
|
13
|
+
if (prefix)
|
|
14
|
+
containers.add(prefix);
|
|
15
|
+
const entries = Array.isArray(node)
|
|
16
|
+
? node.map((v, i) => [String(i), v])
|
|
17
|
+
: Object.entries(node);
|
|
18
|
+
for (const [k, v] of entries) {
|
|
19
|
+
flattenValue(v, prefix ? `${prefix}.${k}` : k, file, leaves, containers);
|
|
20
|
+
}
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const value = node;
|
|
24
|
+
leaves.set(prefix, { value: value === null ? '' : String(value), kind: kindOf(value), file });
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=flatten.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flatten.js","sourceRoot":"","sources":["../../src/formats/flatten.ts"],"names":[],"mappings":"AAUA,SAAS,MAAM,CAAC,KAAuC;IACrD,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC/C,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACjD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAC1B,IAAa,EACb,MAAc,EACd,IAAY,EACZ,MAAyB,EACzB,UAAuB;IAEvB,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9C,IAAI,MAAM;YAAE,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YACjC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAU,CAAC;YAC7C,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC,CAAC;QACpD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;YAC7B,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;QAC3E,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,KAAK,GAAG,IAAwC,CAAC;IACvD,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;AAChG,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Edit, WriteOutcome } from './write.js';
|
|
2
|
+
/**
|
|
3
|
+
* JSON is the one format that can be re-serialised without losing anything —
|
|
4
|
+
* it has no comments, no anchors and no styles to preserve. Key order is kept
|
|
5
|
+
* because setting an existing key leaves it where it is, and new keys append.
|
|
6
|
+
*/
|
|
7
|
+
export declare function writeJsonLocale(content: string, file: string, _locale: string, edits: Edit[]): WriteOutcome;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { FormatError } from './error.js';
|
|
2
|
+
/**
|
|
3
|
+
* JSON is the one format that can be re-serialised without losing anything —
|
|
4
|
+
* it has no comments, no anchors and no styles to preserve. Key order is kept
|
|
5
|
+
* because setting an existing key leaves it where it is, and new keys append.
|
|
6
|
+
*/
|
|
7
|
+
export function writeJsonLocale(content, file, _locale, edits) {
|
|
8
|
+
let root = {};
|
|
9
|
+
if (content.trim() !== '') {
|
|
10
|
+
let parsed;
|
|
11
|
+
try {
|
|
12
|
+
parsed = JSON.parse(content);
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
throw new FormatError(file, err instanceof Error ? err.message : String(err));
|
|
16
|
+
}
|
|
17
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
18
|
+
throw new FormatError(file, 'expected an object at the top level');
|
|
19
|
+
}
|
|
20
|
+
root = parsed;
|
|
21
|
+
}
|
|
22
|
+
const skipped = [];
|
|
23
|
+
for (const edit of edits) {
|
|
24
|
+
const path = edit.key.split('.');
|
|
25
|
+
let node = root;
|
|
26
|
+
let blocked = null;
|
|
27
|
+
for (const [index, segment] of path.entries()) {
|
|
28
|
+
if (index === path.length - 1) {
|
|
29
|
+
const existing = node[segment];
|
|
30
|
+
if (existing !== undefined && typeof existing === 'object' && existing !== null) {
|
|
31
|
+
blocked = `${segment} holds a collection, not a string`;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
node[segment] = edit.value;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
const next = node[segment];
|
|
38
|
+
if (next === undefined) {
|
|
39
|
+
const created = {};
|
|
40
|
+
node[segment] = created;
|
|
41
|
+
node = created;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (typeof next !== 'object' || next === null || Array.isArray(next)) {
|
|
45
|
+
blocked = `${segment} already holds a value`;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
node = next;
|
|
49
|
+
}
|
|
50
|
+
if (blocked)
|
|
51
|
+
skipped.push({ key: edit.key, reason: blocked });
|
|
52
|
+
}
|
|
53
|
+
return { content: `${JSON.stringify(root, null, 2)}\n`, skipped };
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=json-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-write.js","sourceRoot":"","sources":["../../src/formats/json-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,IAAY,EACZ,OAAe,EACf,KAAa;IAEb,IAAI,IAAI,GAA4B,EAAE,CAAC;IAEvC,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1B,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,WAAW,CAAC,IAAI,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAChF,CAAC;QACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3E,MAAM,IAAI,WAAW,CAAC,IAAI,EAAE,qCAAqC,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,GAAG,MAAiC,CAAC;IAC3C,CAAC;IAED,MAAM,OAAO,GAA2C,EAAE,CAAC;IAE3D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,IAAI,GAAG,IAAI,CAAC;QAChB,IAAI,OAAO,GAAkB,IAAI,CAAC;QAElC,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9C,IAAI,KAAK,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;gBAC/B,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;oBAChF,OAAO,GAAG,GAAG,OAAO,mCAAmC,CAAC;oBACxD,MAAM;gBACR,CAAC;gBACD,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;gBAC3B,MAAM;YACR,CAAC;YAED,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;YAC3B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,MAAM,OAAO,GAA4B,EAAE,CAAC;gBAC5C,IAAI,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;gBACxB,IAAI,GAAG,OAAO,CAAC;gBACf,SAAS;YACX,CAAC;YACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrE,OAAO,GAAG,GAAG,OAAO,wBAAwB,CAAC;gBAC7C,MAAM;YACR,CAAC;YACD,IAAI,GAAG,IAA+B,CAAC;QACzC,CAAC;QAED,IAAI,OAAO;YAAE,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC;AACpE,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ReadTarget } from '../types.js';
|
|
2
|
+
import { FormatError } from './error.js';
|
|
3
|
+
export declare class ParseError extends FormatError {
|
|
4
|
+
}
|
|
5
|
+
/** Flattens one JSON locale file into dot-notation keys under an optional namespace. */
|
|
6
|
+
export declare function readJsonLocale(file: string, namespace: string, _locale: string, _isSource: boolean, target: ReadTarget): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { FormatError } from './error.js';
|
|
3
|
+
import { flattenValue } from './flatten.js';
|
|
4
|
+
export class ParseError extends FormatError {
|
|
5
|
+
}
|
|
6
|
+
/** Flattens one JSON locale file into dot-notation keys under an optional namespace. */
|
|
7
|
+
export function readJsonLocale(file, namespace, _locale, _isSource, target) {
|
|
8
|
+
let parsed;
|
|
9
|
+
try {
|
|
10
|
+
parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
throw new ParseError(file, err instanceof Error ? err.message : String(err));
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(parsed)) {
|
|
16
|
+
target.skipped.push({ file, reason: 'the file holds a list, not keyed translations' });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
flattenValue(parsed, namespace, file, target.leaves, target.containers);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/formats/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,OAAO,UAAW,SAAQ,WAAW;CAAG;AAE9C,wFAAwF;AACxF,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,SAAiB,EACjB,OAAe,EACf,SAAkB,EAClB,MAAkB;IAElB,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,UAAU,CAAC,IAAI,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,+CAA+C,EAAE,CAAC,CAAC;QACvF,OAAO;IACT,CAAC;IACD,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Edit, type WriteOutcome } from './write.js';
|
|
2
|
+
/**
|
|
3
|
+
* A PHP string literal for an arbitrary value.
|
|
4
|
+
*
|
|
5
|
+
* Single quotes are preferred because nothing interpolates inside them: `$` and
|
|
6
|
+
* Laravel's `:name` stay literal, which is exactly what a language file wants.
|
|
7
|
+
* Control characters have no single-quoted spelling, so those fall back to
|
|
8
|
+
* double quotes with escapes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function phpLiteral(value: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* An empty language file shaped like the one it is being translated from, so a
|
|
13
|
+
* project written with `array()` does not suddenly gain a file that is not.
|
|
14
|
+
*/
|
|
15
|
+
export declare function blankPhpFile(sourceContent: string | null): string;
|
|
16
|
+
export declare function writePhpLocale(content: string, file: string, _locale: string, edits: Edit[]): WriteOutcome;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { parsePhpLayout } from './php.js';
|
|
2
|
+
import { spliceAll } from './write.js';
|
|
3
|
+
/** Nested levels that have to be created get this much extra indentation. */
|
|
4
|
+
const INDENT_STEP = ' ';
|
|
5
|
+
const SHORT_ESCAPES = new Map([
|
|
6
|
+
[0x0a, '\\n'],
|
|
7
|
+
[0x0d, '\\r'],
|
|
8
|
+
[0x09, '\\t'],
|
|
9
|
+
[0x0b, '\\v'],
|
|
10
|
+
[0x0c, '\\f'],
|
|
11
|
+
[0x1b, '\\e'],
|
|
12
|
+
]);
|
|
13
|
+
function hasControlCharacter(value) {
|
|
14
|
+
for (const ch of value) {
|
|
15
|
+
if (ch.codePointAt(0) < 0x20)
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A PHP string literal for an arbitrary value.
|
|
22
|
+
*
|
|
23
|
+
* Single quotes are preferred because nothing interpolates inside them: `$` and
|
|
24
|
+
* Laravel's `:name` stay literal, which is exactly what a language file wants.
|
|
25
|
+
* Control characters have no single-quoted spelling, so those fall back to
|
|
26
|
+
* double quotes with escapes.
|
|
27
|
+
*/
|
|
28
|
+
export function phpLiteral(value) {
|
|
29
|
+
if (!hasControlCharacter(value)) {
|
|
30
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
31
|
+
}
|
|
32
|
+
let out = '';
|
|
33
|
+
for (const ch of value) {
|
|
34
|
+
const cp = ch.codePointAt(0);
|
|
35
|
+
if (cp >= 0x20) {
|
|
36
|
+
if (ch === '\\' || ch === '"' || ch === '$')
|
|
37
|
+
out += `\\${ch}`;
|
|
38
|
+
else
|
|
39
|
+
out += ch;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
out += SHORT_ESCAPES.get(cp) ?? `\\x${cp.toString(16).padStart(2, '0')}`;
|
|
43
|
+
}
|
|
44
|
+
return `"${out}"`;
|
|
45
|
+
}
|
|
46
|
+
const OPEN = { bracket: '[', array: 'array(' };
|
|
47
|
+
const CLOSE = { bracket: ']', array: ')' };
|
|
48
|
+
/** `'a' => ['b' => 'value'],` for however many levels are missing. */
|
|
49
|
+
function nestedEntry(keys, value, indent, style) {
|
|
50
|
+
const [head, ...rest] = keys;
|
|
51
|
+
if (rest.length === 0)
|
|
52
|
+
return `${indent}${phpLiteral(head)} => ${phpLiteral(value)},`;
|
|
53
|
+
return [
|
|
54
|
+
`${indent}${phpLiteral(head)} => ${OPEN[style]}`,
|
|
55
|
+
nestedEntry(rest, value, indent + INDENT_STEP, style),
|
|
56
|
+
`${indent}${CLOSE[style]},`,
|
|
57
|
+
].join('\n');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* An empty language file shaped like the one it is being translated from, so a
|
|
61
|
+
* project written with `array()` does not suddenly gain a file that is not.
|
|
62
|
+
*/
|
|
63
|
+
export function blankPhpFile(sourceContent) {
|
|
64
|
+
let style = 'bracket';
|
|
65
|
+
if (sourceContent !== null) {
|
|
66
|
+
try {
|
|
67
|
+
style = parsePhpLayout(sourceContent, '<source>').layout.arrays.get('')?.style ?? 'bracket';
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// An unreadable source is no reason to refuse to create the target.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return `<?php\n\nreturn ${OPEN[style]}\n${CLOSE[style]};\n`;
|
|
74
|
+
}
|
|
75
|
+
/** The deepest array that already exists on the way to `key`. */
|
|
76
|
+
function parentFor(arrays, key) {
|
|
77
|
+
const parts = key.split('.');
|
|
78
|
+
for (let depth = parts.length - 1; depth >= 0; depth--) {
|
|
79
|
+
const slot = arrays.get(parts.slice(0, depth).join('.'));
|
|
80
|
+
if (slot)
|
|
81
|
+
return { slot, remaining: parts.slice(depth) };
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
export function writePhpLocale(content, file, _locale, edits) {
|
|
86
|
+
const { layout } = parsePhpLayout(content, file);
|
|
87
|
+
const skipped = [];
|
|
88
|
+
const splices = [];
|
|
89
|
+
// Several new keys can belong in the same array and share one insertion
|
|
90
|
+
// point, so they are collected and emitted together, in order.
|
|
91
|
+
const insertions = new Map();
|
|
92
|
+
for (const edit of edits) {
|
|
93
|
+
const span = layout.values.get(edit.key);
|
|
94
|
+
if (span) {
|
|
95
|
+
splices.push({ start: span.start, end: span.end, text: phpLiteral(edit.value) });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (layout.arrays.has(edit.key)) {
|
|
99
|
+
skipped.push({ key: edit.key, reason: 'the key holds an array, not a string' });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const parent = parentFor(layout.arrays, edit.key);
|
|
103
|
+
if (!parent) {
|
|
104
|
+
skipped.push({ key: edit.key, reason: 'no array to insert into' });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const entry = nestedEntry(parent.remaining, edit.value, parent.slot.indent, parent.slot.style);
|
|
108
|
+
const bucket = insertions.get(parent.slot.insertAt);
|
|
109
|
+
if (bucket)
|
|
110
|
+
bucket.lines.push(entry);
|
|
111
|
+
else
|
|
112
|
+
insertions.set(parent.slot.insertAt, { slot: parent.slot, lines: [entry] });
|
|
113
|
+
}
|
|
114
|
+
for (const [at, { slot, lines }] of insertions) {
|
|
115
|
+
const body = lines.join('\n');
|
|
116
|
+
// An empty array usually already has a newline before its closing bracket;
|
|
117
|
+
// adding a second one would leave a blank line in the file.
|
|
118
|
+
const closingOnNextLine = content.slice(at).startsWith('\n');
|
|
119
|
+
const text = slot.empty
|
|
120
|
+
? closingOnNextLine
|
|
121
|
+
? `\n${body}`
|
|
122
|
+
: `\n${body}\n`
|
|
123
|
+
: slot.hasTrailingComma
|
|
124
|
+
? `\n${body}`
|
|
125
|
+
: `,\n${body}`;
|
|
126
|
+
splices.push({ start: at, end: at, text });
|
|
127
|
+
}
|
|
128
|
+
return { content: spliceAll(content, splices), skipped };
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=php-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"php-write.js","sourceRoot":"","sources":["../../src/formats/php-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ReadTarget } from '../types.js';
|
|
2
|
+
import { FormatError } from './error.js';
|
|
3
|
+
import { type PlainValue } from './flatten.js';
|
|
4
|
+
/**
|
|
5
|
+
* Parses the `<?php return [...];` subset that Laravel language files use.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately a parser and not an evaluator: locale files come from the
|
|
8
|
+
* repository being linted, so running them would mean executing untrusted code,
|
|
9
|
+
* and it would force PHP onto every machine running the linter. Anything
|
|
10
|
+
* outside the literal subset is a clear error rather than a guess.
|
|
11
|
+
*/
|
|
12
|
+
export declare class PhpParseError extends FormatError {
|
|
13
|
+
constructor(file: string, line: number, message: string);
|
|
14
|
+
}
|
|
15
|
+
/** Where a value sits in the source text, so it can be replaced in place. */
|
|
16
|
+
export interface ValueSpan {
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
}
|
|
20
|
+
/** `array(...)` still turns up in older projects; new entries should match. */
|
|
21
|
+
export type ArrayStyle = 'bracket' | 'array';
|
|
22
|
+
/** Where a new entry can be inserted into an existing array. */
|
|
23
|
+
export interface ArraySlot {
|
|
24
|
+
style: ArrayStyle;
|
|
25
|
+
/** Just past the last entry's value, or just past the opening bracket. */
|
|
26
|
+
insertAt: number;
|
|
27
|
+
/** Indentation the entries use, so an inserted one lines up. */
|
|
28
|
+
indent: string;
|
|
29
|
+
hasTrailingComma: boolean;
|
|
30
|
+
empty: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface PhpLayout {
|
|
33
|
+
/** Dot path -> the span of its value expression. */
|
|
34
|
+
values: Map<string, ValueSpan>;
|
|
35
|
+
/** Dot path of every array ('' for the returned one) -> where to insert. */
|
|
36
|
+
arrays: Map<string, ArraySlot>;
|
|
37
|
+
}
|
|
38
|
+
export declare function parsePhp(text: string, file: string): PlainValue;
|
|
39
|
+
/** Parses and also reports where every value and array sits in the text. */
|
|
40
|
+
export declare function parsePhpLayout(text: string, file: string): {
|
|
41
|
+
value: PlainValue;
|
|
42
|
+
layout: PhpLayout;
|
|
43
|
+
};
|
|
44
|
+
export declare function readPhpLocale(file: string, namespace: string, _locale: string, _isSource: boolean, target: ReadTarget): void;
|