Skip to content

Commit

Permalink
feat: adding option "sourceFileOnly" to config "includeContext"
Browse files Browse the repository at this point in the history
fixes #45
  • Loading branch information
daniel-sc committed Sep 7, 2022
1 parent af5a87c commit 9cb191a
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 25 deletions.
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,22 @@ ng extract-i18n # yes, same as before - this replaces the original builder

In your `angular.json` the target `extract-i18n` that can be configured with the following options:

| Name | Default | Description |
|------------------------------|-------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `browserTarget` | Inferred from current setup by `ng add` | A browser builder target to extract i18n messages in the format of `project:target[:configuration]`. See https://angular.io/cli/extract-i18n#options |
| `format` | Inferred from current setup by `ng add` | Any of `xlf`, `xlif`, `xliff`, `xlf2`, `xliff2` |
| `outputPath` | Inferred from current setup by `ng add` | Path to folder containing all (source and target) translation files. |
| `targetFiles` | Inferred from current setup by `ng add` | Filenames (relative to `outputPath` of all target translation files (e.g. `["messages.fr.xlf", "messages.de.xlf"]`). |
| `sourceLanguageTargetFile` | Unused | If this is set (to one of the `targetFiles`), new translations in that target file will be set to `state="final"` (instead of default `state="new"`). |
| `sourceFile` | `messages.xlf`. `ng add` tries to infer non default setups. | Filename (relative to `outputPath` of source translation file (e.g. `"translations-source.xlf"`). |
| `removeIdsWithPrefix` | `[]` | List of prefix strings. All translation units with matching `id` attribute are removed. Useful for excluding duplicate library translations. |
| `fuzzyMatch` | `true` | Whether translation units without matching IDs are fuzzy matched by source text. |
| `resetTranslationState` | `true` | Reset the translation state to new/initial for new/changed units. |
| `collapseWhitespace` | `true` | Collapsing of multiple whitespaces/line breaks in translation sources and targets. |
| `trim` | `false` | Trim translation sources and targets. |
| `includeContext` | `false` | Whether to include the context information (like notes) in the translation files. This is useful for sending the target translation files to translation agencies/services. |
| `newTranslationTargetsBlank` | `false` | When `false` (default) the "target" of new translation units is set to the "source" value. When `true`, an empty string is used. When `'omit'`, no target element is created. |
| `sort` | `"stableAppendNew"` | Sorting of all translation units in source and target translation files. Supported: `"idAsc"` (sort by translation IDs), `"stableAppendNew"` (keep existing sorting, append new translations at the end) |
| Name | Default | Description |
|------------------------------|-------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `browserTarget` | Inferred from current setup by `ng add` | A browser builder target to extract i18n messages in the format of `project:target[:configuration]`. See https://angular.io/cli/extract-i18n#options |
| `format` | Inferred from current setup by `ng add` | Any of `xlf`, `xlif`, `xliff`, `xlf2`, `xliff2` |
| `outputPath` | Inferred from current setup by `ng add` | Path to folder containing all (source and target) translation files. |
| `targetFiles` | Inferred from current setup by `ng add` | Filenames (relative to `outputPath` of all target translation files (e.g. `["messages.fr.xlf", "messages.de.xlf"]`). |
| `sourceLanguageTargetFile` | Unused | If this is set (to one of the `targetFiles`), new translations in that target file will be set to `state="final"` (instead of default `state="new"`). |
| `sourceFile` | `messages.xlf`. `ng add` tries to infer non default setups. | Filename (relative to `outputPath` of source translation file (e.g. `"translations-source.xlf"`). |
| `removeIdsWithPrefix` | `[]` | List of prefix strings. All translation units with matching `id` attribute are removed. Useful for excluding duplicate library translations. |
| `fuzzyMatch` | `true` | Whether translation units without matching IDs are fuzzy matched by source text. |
| `resetTranslationState` | `true` | Reset the translation state to new/initial for new/changed units. |
| `collapseWhitespace` | `true` | Collapsing of multiple whitespaces/line breaks in translation sources and targets. |
| `trim` | `false` | Trim translation sources and targets. |
| `includeContext` | `false` | Whether to include the context information (like notes) in the translation files. This is useful for sending the target translation files to translation agencies/services. When `sourceFileOnly` the context is retained only in the `sourceFile`. |
| `newTranslationTargetsBlank` | `false` | When `false` (default) the "target" of new translation units is set to the "source" value. When `true`, an empty string is used. When `'omit'`, no target element is created. |
| `sort` | `"stableAppendNew"` | Sorting of all translation units in source and target translation files. Supported: `"idAsc"` (sort by translation IDs), `"stableAppendNew"` (keep existing sorting, append new translations at the end) |

## Contribute

Expand Down
114 changes: 113 additions & 1 deletion src/builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,55 @@ describe('Builder', () => {
});
});

test('extract-and-merge xlf 1.2 with newTranslationTargetsBlank=omit', async () => {
await runTest(
{
messagesBefore: '<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">\n' +
' <file source-language="de" datatype="plaintext" original="ng2.template">\n' +
' <body>\n' +
' <trans-unit id="ID1" datatype="html">\n' +
' <source>source val</source>\n' +
' </trans-unit>\n' +
' <trans-unit id="ID2" datatype="html">\n' +
' <source>source val2</source>\n' +
' </trans-unit>\n' +
' </body>\n' +
' </file>\n' +
'</xliff>',
messagesFrBefore: '<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">\n' +
' <file source-language="de" target-language="fr-ch" datatype="plaintext" original="ng2.template">\n' +
' <body>\n' +
' <trans-unit id="ID1" datatype="html">\n' +
' <source>source val</source>\n' +
' <target state="translated">target val</target>\n' +
' </trans-unit>\n' +
' </body>\n' +
' </file>\n' +
'</xliff>',
options: {
format: 'xlf',
targetFiles: ['messages.fr.xlf'],
sourceLanguageTargetFile: "messages.fr.xlf",
outputPath: 'builder-test',
removeIdsWithPrefix: ['removeMe'],
newTranslationTargetsBlank: 'omit'
},
messagesFrExpected: '<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">\n' +
' <file source-language="de" target-language="fr-ch" datatype="plaintext" original="ng2.template">\n' +
' <body>\n' +
' <trans-unit id="ID1" datatype="html">\n' +
' <source>source val</source>\n' +
' <target state="translated">target val</target>\n' +
' </trans-unit>\n' +
' <trans-unit id="ID2" datatype="html">\n' +
' <source>source val2</source>\n' +
' </trans-unit>\n' +
' </body>\n' +
' </file>\n' +
'</xliff>'
});
});

test('extract-and-merge with xml definition without newline', async () => {
await runTest(
{
Expand Down Expand Up @@ -1102,7 +1151,7 @@ describe('Builder', () => {
' </trans-unit>\n' +
' </body>\n' +
' </file>\n' +
'</xliff>'
'</xliff>';
test('add new context groups', async () => {
await runTest(
{
Expand Down Expand Up @@ -1243,6 +1292,69 @@ describe('Builder', () => {
});
});

test('retain context in sourceFile only, when includeContext=sourceFileOnly', async () => {
const messagesBefore = '<?xml version="1.0"?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">\n' +
' <file source-language="de" datatype="plaintext" original="ng2.template">\n' +
' <body>\n' +
' <trans-unit id="ID1" datatype="html">\n' +
' <source>Some text</source>\n' +
' <context-group purpose="location">\n' +
' <context context-type="sourcefile">src/app/app-routing.module.ts</context>\n' +
' <context context-type="linenumber">12</context>\n' +
' </context-group>\n' +
' </trans-unit>\n' +
' <trans-unit id="ID2" datatype="html">\n' +
' <source>Some text2</source>\n' +
' <context-group purpose="location">\n' +
' <context context-type="sourcefile">src/app/app.component.html</context>\n' +
' <context context-type="linenumber">4</context>\n' +
' </context-group>\n' +
' </trans-unit>\n' +
' </body>\n' +
' </file>\n' +
'</xliff>';
await runTest(
{
messagesBefore: messagesBefore,
messagesFrBefore: '<?xml version="1.0"?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">\n' +
' <file source-language="de" target-language="fr-ch" datatype="plaintext" original="ng2.template">\n' +
' <body>\n' +
' <trans-unit id="ID1" datatype="html">\n' +
' <source>Some text</source>\n' +
' <target state="new">Some text</target>\n' +
' <context-group purpose="location">\n' +
' <context context-type="sourcefile">src/app/app-routing.module.ts</context>\n' +
' <context context-type="linenumber">12</context>\n' +
' </context-group>\n' +
' </trans-unit>\n' +
' </body>\n' +
' </file>\n' +
'</xliff>',
options: {
format: 'xlf',
targetFiles: ['messages.fr.xlf'],
includeContext: 'sourceFileOnly',
outputPath: 'builder-test'
},
messagesExpected: messagesBefore,
messagesFrExpected: '<?xml version="1.0"?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">\n' +
' <file source-language="de" target-language="fr-ch" datatype="plaintext" original="ng2.template">\n' +
' <body>\n' +
' <trans-unit id="ID1" datatype="html">\n' +
' <source>Some text</source>\n' +
' <target state="new">Some text</target>\n' +
' </trans-unit>\n' +
' <trans-unit id="ID2" datatype="html">\n' +
' <source>Some text2</source>\n' +
' <target state="new">Some text2</target>\n' +
' </trans-unit>\n' +
' </body>\n' +
' </file>\n' +
'</xliff>'
}
);
});

test('retain whitespace between interpolations', async () => {
await runTest(
{
Expand Down
20 changes: 14 additions & 6 deletions src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface Options extends JsonObject {
resetTranslationState: boolean,
collapseWhitespace: boolean,
trim: boolean,
includeContext: boolean,
includeContext: boolean | 'sourceFileOnly',
newTranslationTargetsBlank: boolean | 'omit',
sort: 'idAsc' | 'stableAppendNew',
browserTarget: string
Expand Down Expand Up @@ -92,9 +92,17 @@ async function extractI18nMergeBuilder(options: Options, context: BuilderContext

context.logger.info(`normalize ${sourcePath} ...`);
const translationSourceFile = await fs.readFile(sourcePath, 'utf8');
const removePaths = [
...(options.includeContext ? [] : [isXliffV2 ? '/xliff/file/unit/notes' : '/xliff/file/body/trans-unit/context-group']),
...(options.removeIdsWithPrefix ?? []).map(removePrefix => isXliffV2 ? `/xliff/file/unit[starts-with(@id,"${removePrefix}")]` : `/xliff/file/body/trans-unit[starts-with(@id,"${removePrefix}")]`)

const removeIdsWithPrefixPaths = (options.removeIdsWithPrefix ?? []).map(removePrefix => isXliffV2 ? `/xliff/file/unit[starts-with(@id,"${removePrefix}")]` : `/xliff/file/body/trans-unit[starts-with(@id,"${removePrefix}")]`);
const removeContextPaths = (includeContext: boolean) => includeContext ? [] : [isXliffV2 ? '/xliff/file/unit/notes' : '/xliff/file/body/trans-unit/context-group'];

const removePathsSourceFile = [
...(removeContextPaths(options.includeContext === true || options.includeContext === 'sourceFileOnly')),
...removeIdsWithPrefixPaths
];
const removePathsTargetFiles = [
...(removeContextPaths(options.includeContext === true)),
...removeIdsWithPrefixPaths
];
const idPath = isXliffV2 ? '/xliff/file/unit/@id' : '/xliff/file/body/trans-unit/@id';
const sort: Options['sort'] = options.sort ?? 'stableAppendNew';
Expand All @@ -103,7 +111,7 @@ async function extractI18nMergeBuilder(options: Options, context: BuilderContext
trim: options.trim ?? false,
normalizeWhitespace: options.collapseWhitespace ?? true,
sortPath: sort === 'idAsc' ? idPath : undefined,
removePath: removePaths
removePath: removePathsSourceFile
});

let idMapping: { [id: string]: string } = {};
Expand All @@ -123,7 +131,7 @@ async function extractI18nMergeBuilder(options: Options, context: BuilderContext
normalizeWhitespace: options.collapseWhitespace,
// no sorting for 'stableAppendNew' as this is the default merge behaviour:
sortPath: sort === 'idAsc' ? idPath : undefined,
removePath: removePaths
removePath: removePathsTargetFiles
});
await fs.writeFile(targetPath, normalizedTarget);
idMapping = {...idMapping, ...mapping};
Expand Down
Loading

0 comments on commit 9cb191a

Please sign in to comment.