@chesterbrains/translify-cli 0.4.2 → 0.6.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/README.md +87 -1
- package/dist/commands/pull.js +24 -1
- package/dist/commands/push.js +5 -0
- package/dist/config.js +11 -0
- package/dist/http.js +3 -0
- package/dist/render.js +33 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/schema/translify.schema.json +20 -1
package/README.md
CHANGED
|
@@ -117,6 +117,89 @@ flutter gen-l10n
|
|
|
117
117
|
|
|
118
118
|
`"@@locale"` is optional in ARB files: when it is absent, the file's locale is used. When it is present it must match the file's locale (case and `_`/`-` are ignored). On `pull`, the CLI writes `@@locale` using the locale name on disk (for example `en_US` in `app_en_US.arb`), as `flutter gen-l10n` expects.
|
|
119
119
|
|
|
120
|
+
## Angular (XLIFF)
|
|
121
|
+
|
|
122
|
+
Angular's `@angular/localize` writes a template interpolation as an inline `<ph>` element and a template ICU as
|
|
123
|
+
`{VAR_PLURAL, …}` / `{VAR_SELECT, …}`, and matches a translation's placeholders by the `equiv` name. Plain ICU text
|
|
124
|
+
(`Hello, {name}`) in an Angular XLIFF is therefore literal text at build time. Set `xliffDialect` on the rule and the
|
|
125
|
+
server converts both ways:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{ "files": [{ "pattern": "src/locale/messages.{locale}.xlf", "format": "xliff", "namespace": "web", "xliffDialect": "angular" }] }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Give every template string `i18n="@@<key>"` so the unit ids are your keys. What `pull` writes:
|
|
132
|
+
|
|
133
|
+
| Translify value | Angular unit |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `Hello, {name}` | `Hello, <ph id="0" equiv="INTERPOLATION" disp="{{ name }}"/>` |
|
|
136
|
+
| `{a} {b} {a}` | ids `0`, `1`, `2`; `equiv` `INTERPOLATION`, `INTERPOLATION_1`, `INTERPOLATION` (one name per distinct argument, in order of first appearance in the source; a translation reuses the source's names however it reorders them) |
|
|
137
|
+
| `{count, plural, one {{count} item} other {# items}}` (the whole message) | `{VAR_PLURAL, plural, one {<ph id="0" equiv="INTERPOLATION" disp="{{ count }}"/> item} other {<ph id="1" equiv="INTERPOLATION" disp="{{ count }}"/> items}}` |
|
|
138
|
+
| `{g, select, male {he} other {they}}` (the whole message) | `{VAR_SELECT, select, male {he} other {they}}` |
|
|
139
|
+
|
|
140
|
+
What `push` reads from `ng extract-i18n --format=xlf2` output:
|
|
141
|
+
|
|
142
|
+
| Angular | Translify argument |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `<ph equiv="INTERPOLATION" disp="{{ name }}"/>` | `{name}` |
|
|
145
|
+
| `disp="{{ count() }}"`, `disp="{{ user.name }}"`, `disp="{{ price | currency }}"` | the head identifier: `{count}`, `{user}`, `{price}` (also through `!.` and `[…]`); two that collide become `{user}` and `{user_2}` |
|
|
146
|
+
| no usable `disp` | `{p0}`, `{p1}`, … from the `equiv` number |
|
|
147
|
+
| `VAR_PLURAL` / `VAR_SELECT` | named after the first interpolation inside its branches, else `count` / `value` |
|
|
148
|
+
|
|
149
|
+
Units Angular hashes for a message that mixes text with an ICU are dropped, and `<note category="location">` is not a
|
|
150
|
+
description. After a round trip `#` comes back as `{count}`, and a plural or select with no interpolation inside keeps
|
|
151
|
+
`count` / `value` as its argument.
|
|
152
|
+
|
|
153
|
+
Not supported in this version (the pull is refused with `XLIFF_DIALECT_UNSUPPORTED`, the push reports the unit): text
|
|
154
|
+
beside a plural or select, two ICUs in one message, a plural or select inside another, `selectordinal`, `number` /
|
|
155
|
+
`date` / `time` formats, a quoted literal brace (`'{'`), HTML inside a message, and keys whose placeholders are printf or
|
|
156
|
+
i18next rather than ICU. Give
|
|
157
|
+
such a key its own element so the ICU is the whole message, or move it to a JSON rule. A complete example is the
|
|
158
|
+
Angular app in [translify-examples](https://github.com/chesterbrains/translify-examples).
|
|
159
|
+
|
|
160
|
+
## Xcode (XLIFF)
|
|
161
|
+
|
|
162
|
+
Xcode exports and imports **XLIFF 1.2** (`<trans-unit>`), keeps printf placeholders (`%@`, `%lld`) literal, and spreads one
|
|
163
|
+
String Catalog entry over several units for plurals and substitutions. Set `xliffDialect: "xcode"` and `xliffOriginal` on the
|
|
164
|
+
rule and the server converts both ways; Translify keeps ICU, so the same catalog serves React, Flutter, Angular and iOS, and
|
|
165
|
+
iOS translations get placeholder validation (Xcode's own import accepts a target that lost its `%@` without a word).
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{ "files": [{ "pattern": "Localization/{locale}.xliff", "format": "xliff", "namespace": "ios",
|
|
169
|
+
"xliffDialect": "xcode", "xliffOriginal": "Sources/App/Localizable.xcstrings" }] }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Workflow:
|
|
173
|
+
|
|
174
|
+
1. Seed once: `xcodebuild -exportLocalizations -localizationPath out -exportLanguage en`, copy
|
|
175
|
+
`out/en.xcloc/Localized Contents/en.xliff` to `Localization/en.xliff`, `translify push`.
|
|
176
|
+
2. Translate in Translify, then `translify pull`.
|
|
177
|
+
3. `xcodebuild -importLocalizations -localizationPath Localization/it.xliff` (a bare `.xliff` works; `<file original>`
|
|
178
|
+
comes from `xliffOriginal`).
|
|
179
|
+
|
|
180
|
+
What `pull` writes (arguments are positional, numbered by first appearance in the source, so a reordered translation keeps
|
|
181
|
+
its meaning; the printf type comes from the push that created the key, else `%@` for text, `%lld` for a plural's count, `%f`
|
|
182
|
+
for a fraction):
|
|
183
|
+
|
|
184
|
+
| Translify value | Units |
|
|
185
|
+
|---|---|
|
|
186
|
+
| `Hello, {name}` | `key` → `Hello, %1$@` |
|
|
187
|
+
| `{count, plural, one {# item} other {# items}}` | `key\|==\|plural.one` → `%1$lld item`, `key\|==\|plural.other` → `%1$lld items` |
|
|
188
|
+
| `You have {count, plural, one {# item} other {# items}} in your cart, {name}` | `key` → `You have %1$#@count@ in your cart, %2$@`, plus `key\|==\|substitutions.count.plural.one` / `.other` |
|
|
189
|
+
| untranslated | the unit has a `<source>` and no `<target>`, which Xcode shows as untranslated |
|
|
190
|
+
|
|
191
|
+
What `push` reads: `%@` / `%1$@` → `{arg1}` (renamed to the key's own argument when the key already exists), `%lld` →
|
|
192
|
+
`{arg2}` or `#` inside a plural, `%.2f` → `{arg, number}`, `%%` → `%`; `|==|plural.*` units → one ICU plural named `count`;
|
|
193
|
+
a `%#@name@` substitution → the plural inside the sentence. The printf types are kept in the key's metadata for the next
|
|
194
|
+
pull. Device variations (`|==|device.*`), unknown unit kinds and legacy `.strings`/`.stringsdict` exports are reported as
|
|
195
|
+
`unsupportedVariation` and skipped.
|
|
196
|
+
|
|
197
|
+
Not supported in this version (the pull is refused with `XLIFF_DIALECT_UNSUPPORTED`): `select` and `selectordinal`, exact
|
|
198
|
+
matches (`=0`) and `offset`, two plurals in one message, nested plurals, number formats without a known printf type (e.g.
|
|
199
|
+
currency), a translation whose shape differs from the source (plain text for a plural), and keys whose
|
|
200
|
+
placeholders are printf or i18next rather than ICU. A complete example is `examples/ios-xcstrings` in
|
|
201
|
+
[translify-examples](https://github.com/chesterbrains/translify-examples).
|
|
202
|
+
|
|
120
203
|
## `translify.json` reference
|
|
121
204
|
|
|
122
205
|
`translify.json` lives in the repository root; every command reads it from the current directory. The
|
|
@@ -132,6 +215,8 @@ fields are refused, and so is any field whose name contains `key`, `secret` or `
|
|
|
132
215
|
| `files[].format` | `json` \| `xliff` \| `arb` | yes | File format: i18next JSON, XLIFF or Flutter ARB. |
|
|
133
216
|
| `files[].namespace` | string | when the pattern has no `{namespace}`, and always for `arb` | Fixed namespace for every file the rule matches. Lowercase kebab-case, at most 64 characters. |
|
|
134
217
|
| `files[].jsonStyle` | `nested` \| `flat` | no | Only for `json`. `nested` (the default) writes `{ "a": { "b": "…" } }`, `flat` writes `{ "a.b": "…" }`. |
|
|
218
|
+
| `files[].xliffDialect` | `angular` \| `xcode` | no | Only for `xliff`. `angular` writes `@angular/localize` placeholders on pull and reads `ng extract-i18n` output on push ([Angular (XLIFF)](#angular-xliff)); `xcode` writes XLIFF 1.2 for String Catalogs and reads `xcodebuild -exportLocalizations` output ([Xcode (XLIFF)](#xcode-xliff)). |
|
|
219
|
+
| `files[].xliffOriginal` | string | with `xliffDialect: "xcode"` | The String Catalog path Xcode knows, e.g. `Sources/App/Localizable.xcstrings`; written to `<file original>`. Xcode imports nothing when it does not match the project. Only with `xcode`. |
|
|
135
220
|
| `locales` | object | no | Maps a locale name on disk to a Translify locale code, e.g. `{ "en_US": "en-US" }`. Unmapped names pass through unchanged. Two names may not map to the same code, and a target may not itself be a mapped name. |
|
|
136
221
|
|
|
137
222
|
Each file must be matched by one rule only, and each (locale, namespace) pair must come from one file only;
|
|
@@ -289,7 +374,7 @@ Pull and publish on deploy:
|
|
|
289
374
|
| 3 | Plan quota exceeded |
|
|
290
375
|
| 4 | Network failure, server error, push too large, CLI too old, transaction conflict, or rate limited after 3 retries |
|
|
291
376
|
| 5 | `publish --fail-on-withheld`: the publish went through, but the review gate withheld some translations |
|
|
292
|
-
| 6 | Placeholder errors: `lint` found them (or more warnings than `--max-warnings`),
|
|
377
|
+
| 6 | Placeholder errors: `lint` found them (or more warnings than `--max-warnings`), `publish` refused because translations with errors would go live, or `pull` refused an Angular XLIFF rule |
|
|
293
378
|
|
|
294
379
|
The server's error `code` decides the exit code first; the HTTP status is only used for codes the CLI does not know.
|
|
295
380
|
|
|
@@ -317,6 +402,7 @@ The server's error `code` decides the exit code first; the HTTP status is only u
|
|
|
317
402
|
| `REVIEW_DISABLED` | 1 | `push --status draft` or `--status needs-review` on a project without review. Turn on **Require review** in the project settings, or drop `--status`. Nothing was written. |
|
|
318
403
|
| `APPROVED_ONLY_REQUIRES_WORKING` | 1 | `--only-approved` was combined with `--from env:<slug>`. Pull an environment without it; it already holds only what its gate allowed. |
|
|
319
404
|
| `INVALID_CELLS` | 6 | `publish` refused: translations that would go live have placeholder errors. Nothing was published; the output lists them like `lint` does. Fix them in Translify, or re-run with `--allow-errors` to publish anyway (recorded in the activity log). |
|
|
405
|
+
| `XLIFF_DIALECT_UNSUPPORTED` | 6 | `pull` refused: a translation cannot be written in the rule's XLIFF dialect (Angular: text beside a plural, nested plurals, number/date formats; Xcode: `select`, `=0`/`offset`, a translation of a different shape; both: printf/i18next keys). Nothing was written; the output lists the cells and why. Split the text, or move the key to a JSON rule. See [Angular (XLIFF)](#angular-xliff) and [Xcode (XLIFF)](#xcode-xliff). |
|
|
320
406
|
| `TRANSACTION_CONFLICT` | 4 | Another write collided with yours. Retry. |
|
|
321
407
|
| `RATE_LIMITED` | 4 | The CLI already retried 3 times, honouring `Retry-After`. Wait a moment and retry. |
|
|
322
408
|
| `INTERNAL_ERROR` / `DATABASE_ERROR` / 5xx | 4 | A server problem. Retry shortly; if it persists, re-run with `--debug` and report it. |
|
package/dist/commands/pull.js
CHANGED
|
@@ -2,8 +2,22 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
3
3
|
import { CliError, EXIT, ONLY_APPROVED_NEEDS_WORKING } from '../errors.js';
|
|
4
4
|
import { pathFor, toFsLocale, toTranslifyLocale } from '../patterns.js';
|
|
5
|
+
import { renderReasonCells } from '../render.js';
|
|
5
6
|
const readOrUndefined = async (path) => readFile(path, 'utf8').catch(() => undefined);
|
|
6
7
|
const NESTED_CONFLICT = /nested JSON cannot hold both/;
|
|
8
|
+
const isDialectUnsupported = (details) => typeof details === 'object' && details !== null && details.code === 'XLIFF_DIALECT_UNSUPPORTED';
|
|
9
|
+
/** The server's one-line refusal, the cells grouped like `lint`, and the way out. */
|
|
10
|
+
const dialectRefusal = (body, config, dialect) => {
|
|
11
|
+
const cells = Array.isArray(body.cells) ? body.cells : [];
|
|
12
|
+
const lines = [
|
|
13
|
+
`Pull refused: ${body.total} translation(s) cannot be written as XLIFF for ${dialect}. Nothing was written.`,
|
|
14
|
+
...renderReasonCells(cells, config),
|
|
15
|
+
];
|
|
16
|
+
if (body.total > cells.length)
|
|
17
|
+
lines.push(`Showing first ${cells.length} of ${body.total}.`);
|
|
18
|
+
lines.push('Split the text so each key is plain text with placeholders or one plural/select, or move the key to a JSON rule.');
|
|
19
|
+
return new CliError(EXIT.issues, lines.join('\n'), body);
|
|
20
|
+
};
|
|
7
21
|
/**
|
|
8
22
|
* Flutter's gen_l10n requires `@@locale` to match the file name, and only the CLI knows the disk
|
|
9
23
|
* mapping. Rewrites the value in place (key order kept) with the server's own formatting so the
|
|
@@ -51,6 +65,10 @@ export async function runPull(opts, deps) {
|
|
|
51
65
|
const query = { format: rule.format };
|
|
52
66
|
if (rule.format === 'json' && rule.jsonStyle !== undefined)
|
|
53
67
|
query.jsonStyle = rule.jsonStyle;
|
|
68
|
+
if (rule.format === 'xliff' && rule.xliffDialect !== undefined)
|
|
69
|
+
query.dialect = rule.xliffDialect;
|
|
70
|
+
if (rule.format === 'xliff' && rule.xliffOriginal !== undefined)
|
|
71
|
+
query.original = rule.xliffOriginal;
|
|
54
72
|
if (opts.from !== undefined)
|
|
55
73
|
query.from = opts.from;
|
|
56
74
|
if (opts.onlyApproved)
|
|
@@ -60,7 +78,12 @@ export async function runPull(opts, deps) {
|
|
|
60
78
|
const namespace = rule.namespace ?? opts.namespace;
|
|
61
79
|
if (namespace !== undefined)
|
|
62
80
|
query.namespaces = namespace;
|
|
63
|
-
const res = await deps.api.get('/cli/v1/pull', query)
|
|
81
|
+
const res = await deps.api.get('/cli/v1/pull', query).catch((error) => {
|
|
82
|
+
if (error instanceof CliError && isDialectUnsupported(error.details)) {
|
|
83
|
+
throw dialectRefusal(error.details, deps.config, rule.xliffDialect ?? 'angular');
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
});
|
|
64
87
|
responses.push(res);
|
|
65
88
|
for (const warning of res.warnings) {
|
|
66
89
|
lines.push(`warning: ${warning}`);
|
package/dist/commands/push.js
CHANGED
|
@@ -20,6 +20,8 @@ const buildForm = (uploads, opts, dryRun, prune, pruneDigest) => {
|
|
|
20
20
|
format: match.rule.format,
|
|
21
21
|
locale: match.locale,
|
|
22
22
|
namespace: match.namespace,
|
|
23
|
+
...(match.rule.format === 'xliff' && match.rule.xliffDialect !== undefined ? { dialect: match.rule.xliffDialect } : {}),
|
|
24
|
+
...(match.rule.format === 'xliff' && match.rule.xliffOriginal !== undefined ? { original: match.rule.xliffOriginal } : {}),
|
|
23
25
|
}))));
|
|
24
26
|
form.set('dryRun', String(dryRun));
|
|
25
27
|
form.set('overwriteTargets', String(opts.overwriteTargets === true));
|
|
@@ -73,6 +75,9 @@ export async function runPush(opts, deps) {
|
|
|
73
75
|
bytes: new Uint8Array(await readFile(join(deps.cwd, match.path))),
|
|
74
76
|
})));
|
|
75
77
|
const show = (res, preview) => {
|
|
78
|
+
if (opts.json !== true)
|
|
79
|
+
for (const warning of res.warnings ?? [])
|
|
80
|
+
deps.out(`warning: ${warning}`);
|
|
76
81
|
deps.out(opts.json === true
|
|
77
82
|
? JSON.stringify({ preview, response: res, summary: summarizePush(res) })
|
|
78
83
|
: renderPush(res, { prune: opts.prune === true }));
|
package/dist/config.js
CHANGED
|
@@ -17,6 +17,8 @@ const fileRule = z
|
|
|
17
17
|
format: z.enum(['json', 'xliff', 'arb']),
|
|
18
18
|
namespace: z.string().regex(NAMESPACE, 'must be lowercase kebab-case').max(64).optional(),
|
|
19
19
|
jsonStyle: z.enum(['nested', 'flat']).optional(),
|
|
20
|
+
xliffDialect: z.enum(['angular', 'xcode']).optional(),
|
|
21
|
+
xliffOriginal: z.string().min(1).max(512).optional(),
|
|
20
22
|
})
|
|
21
23
|
.strict()
|
|
22
24
|
.superRefine((rule, ctx) => {
|
|
@@ -30,6 +32,15 @@ const fileRule = z
|
|
|
30
32
|
if (rule.jsonStyle !== undefined && rule.format !== 'json') {
|
|
31
33
|
ctx.addIssue({ code: 'custom', path: ['jsonStyle'], message: 'only applies to format "json"' });
|
|
32
34
|
}
|
|
35
|
+
if (rule.xliffDialect !== undefined && rule.format !== 'xliff') {
|
|
36
|
+
ctx.addIssue({ code: 'custom', path: ['xliffDialect'], message: 'only applies to format "xliff"' });
|
|
37
|
+
}
|
|
38
|
+
if (rule.xliffOriginal !== undefined && rule.xliffDialect !== 'xcode') {
|
|
39
|
+
ctx.addIssue({ code: 'custom', path: ['xliffOriginal'], message: 'only applies to xliffDialect "xcode"' });
|
|
40
|
+
}
|
|
41
|
+
if (rule.xliffDialect === 'xcode' && rule.xliffOriginal === undefined) {
|
|
42
|
+
ctx.addIssue({ code: 'custom', path: ['xliffOriginal'], message: 'required with xliffDialect "xcode"' });
|
|
43
|
+
}
|
|
33
44
|
})
|
|
34
45
|
.transform((rule) => (rule.format === 'json' ? { ...rule, jsonStyle: rule.jsonStyle ?? 'nested' } : rule));
|
|
35
46
|
const configSchema = z
|
package/dist/http.js
CHANGED
|
@@ -33,6 +33,7 @@ export const CODE_EXIT = {
|
|
|
33
33
|
REVIEW_DISABLED: EXIT.failed,
|
|
34
34
|
APPROVED_ONLY_REQUIRES_WORKING: EXIT.failed,
|
|
35
35
|
INVALID_CELLS: EXIT.issues,
|
|
36
|
+
XLIFF_DIALECT_UNSUPPORTED: EXIT.issues,
|
|
36
37
|
};
|
|
37
38
|
const exitFor = (status, code) => {
|
|
38
39
|
const byCode = code === undefined ? undefined : CODE_EXIT[code];
|
|
@@ -91,6 +92,8 @@ const describeBody = (status, body) => {
|
|
|
91
92
|
return ONLY_APPROVED_NEEDS_WORKING;
|
|
92
93
|
case 'INVALID_CELLS':
|
|
93
94
|
return `Publish refused: ${String(body.total)} translation(s) with placeholder errors would go live. Nothing was published.`;
|
|
95
|
+
case 'XLIFF_DIALECT_UNSUPPORTED':
|
|
96
|
+
return `Pull refused: ${String(body.total)} translation(s) cannot be written as Angular XLIFF. Nothing was written.`;
|
|
94
97
|
case 'TRANSACTION_CONFLICT':
|
|
95
98
|
return 'The server hit a transaction conflict. Try again.';
|
|
96
99
|
case 'RATE_LIMITED':
|
package/dist/render.js
CHANGED
|
@@ -134,6 +134,39 @@ export function renderIssueCells(cells, config) {
|
|
|
134
134
|
}
|
|
135
135
|
return lines;
|
|
136
136
|
}
|
|
137
|
+
const REASON_TEXT = {
|
|
138
|
+
mixedIcu: 'text next to a plural or select (Angular needs the whole message to be the ICU)',
|
|
139
|
+
multipleIcu: 'more than one plural or select in one message',
|
|
140
|
+
nestedIcu: 'a plural or select inside another one',
|
|
141
|
+
ordinal: 'selectordinal has no Angular equivalent',
|
|
142
|
+
invalidIcu: 'does not parse as ICU',
|
|
143
|
+
formattedArg: 'a number, date or time format has no Angular placeholder',
|
|
144
|
+
literalBrace: "a quoted literal brace ('{') cannot be loaded by Angular",
|
|
145
|
+
syntax: 'the key uses printf or i18next placeholders, not ICU',
|
|
146
|
+
exactMatch: 'an exact match (=0, =1) or offset has no Xcode plural category',
|
|
147
|
+
shapeMismatch: 'the translation is not the same shape as the source (plural vs plain text, or a different plural argument)',
|
|
148
|
+
unsupportedVariation: 'a device variation or an unknown |==| unit (not supported)',
|
|
149
|
+
};
|
|
150
|
+
/** Like renderIssueCells: namespace, key, then one line per cell with the locale as named on disk. */
|
|
151
|
+
export function renderReasonCells(cells, config) {
|
|
152
|
+
const lines = [];
|
|
153
|
+
let namespace;
|
|
154
|
+
let key;
|
|
155
|
+
for (const cell of cells) {
|
|
156
|
+
if (cell.namespace !== namespace) {
|
|
157
|
+
namespace = cell.namespace;
|
|
158
|
+
key = undefined;
|
|
159
|
+
lines.push(pc.bold(namespace));
|
|
160
|
+
}
|
|
161
|
+
if (cell.key !== key) {
|
|
162
|
+
key = cell.key;
|
|
163
|
+
lines.push(` ${key}`);
|
|
164
|
+
}
|
|
165
|
+
const locale = (config === undefined ? cell.locale : toFsLocale(cell.locale, config)).padEnd(8);
|
|
166
|
+
lines.push(` ${locale} ${pc.red('error'.padEnd(8))} ${REASON_TEXT[cell.reason] ?? cell.reason}`);
|
|
167
|
+
}
|
|
168
|
+
return lines;
|
|
169
|
+
}
|
|
137
170
|
export function renderLint(res, config, view = {}) {
|
|
138
171
|
const { errors, warnings } = res.summary;
|
|
139
172
|
if (errors + warnings === 0)
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -28,7 +28,17 @@
|
|
|
28
28
|
"maxLength": 64,
|
|
29
29
|
"description": "Fixed namespace. Required when the pattern has no {namespace}, and always for arb."
|
|
30
30
|
},
|
|
31
|
-
"jsonStyle": { "enum": ["nested", "flat"], "description": "Only for format json. Defaults to nested." }
|
|
31
|
+
"jsonStyle": { "enum": ["nested", "flat"], "description": "Only for format json. Defaults to nested." },
|
|
32
|
+
"xliffDialect": {
|
|
33
|
+
"enum": ["angular", "xcode"],
|
|
34
|
+
"description": "Only for format xliff. angular writes @angular/localize placeholders (<ph equiv=\"INTERPOLATION\"/>, VAR_PLURAL) on pull and reads ng extract-i18n output on push. xcode writes XLIFF 1.2 for String Catalogs (positional printf, |==| plural and substitution units) and reads xcodebuild -exportLocalizations output."
|
|
35
|
+
},
|
|
36
|
+
"xliffOriginal": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"minLength": 1,
|
|
39
|
+
"maxLength": 512,
|
|
40
|
+
"description": "Only with xliffDialect xcode, and required with it: the String Catalog path Xcode knows (e.g. Sources/App/Localizable.xcstrings), written to <file original>. Xcode imports nothing when it does not match."
|
|
41
|
+
}
|
|
32
42
|
},
|
|
33
43
|
"allOf": [
|
|
34
44
|
{
|
|
@@ -42,6 +52,15 @@
|
|
|
42
52
|
{
|
|
43
53
|
"if": { "properties": { "format": { "enum": ["xliff", "arb"] } }, "required": ["format"] },
|
|
44
54
|
"then": { "not": { "required": ["jsonStyle"] } }
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"if": { "properties": { "format": { "enum": ["json", "arb"] } }, "required": ["format"] },
|
|
58
|
+
"then": { "not": { "required": ["xliffDialect"] } }
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"if": { "properties": { "xliffDialect": { "const": "xcode" } }, "required": ["xliffDialect"] },
|
|
62
|
+
"then": { "required": ["xliffOriginal"] },
|
|
63
|
+
"else": { "not": { "required": ["xliffOriginal"] } }
|
|
45
64
|
}
|
|
46
65
|
]
|
|
47
66
|
}
|