diff --git a/l10n-dev/package-lock.json b/l10n-dev/package-lock.json index f41e39d..507fcc3 100644 --- a/l10n-dev/package-lock.json +++ b/l10n-dev/package-lock.json @@ -1,12 +1,12 @@ { "name": "@vscode/l10n-dev", - "version": "0.0.9", + "version": "0.0.11", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@vscode/l10n-dev", - "version": "0.0.9", + "version": "0.0.11", "license": "MIT", "dependencies": { "deepmerge-json": "^1.5.0", diff --git a/l10n-dev/package.json b/l10n-dev/package.json index d574ede..8392d4f 100644 --- a/l10n-dev/package.json +++ b/l10n-dev/package.json @@ -1,6 +1,6 @@ { "name": "@vscode/l10n-dev", - "version": "0.0.9", + "version": "0.0.11", "description": "Development time npm module to generate strings bundles from TypeScript files", "author": "Microsoft Corporation", "license": "MIT", diff --git a/l10n-dev/src/cli.ts b/l10n-dev/src/cli.ts index adac4cb..d34924b 100644 --- a/l10n-dev/src/cli.ts +++ b/l10n-dev/src/cli.ts @@ -13,6 +13,7 @@ import { hideBin } from 'yargs/helpers'; import { l10nJsonFormat } from "./common"; yargs(hideBin(process.argv)) +.scriptName("vscode-l10n-dev") .usage('$0 [args]') .command( 'export [args] ', @@ -84,12 +85,12 @@ yargs(hideBin(process.argv)) function l10nExportStrings(paths: string[], outDir: string): void { console.log('Searching for TypeScript files...'); - const matches = paths.map(p => glob.sync(p)).flat(); + const matches = paths.map(p => glob.sync(toPosixPath(p))).flat(); const tsFileContents = matches.reduce((prev, curr) => { if (curr.endsWith('.ts')) { prev.push(readFileSync(path.resolve(curr), 'utf8')); } - const results = glob.sync(path.join(curr, `{,!(node_modules)/**}`, '*.ts')); + const results = glob.sync(path.posix.join(curr, `{,!(node_modules)/**}`, '*.ts')); for (const result of results) { prev.push(readFileSync(path.resolve(result), 'utf8')); } @@ -116,7 +117,7 @@ function l10nExportStrings(paths: string[], outDir: string): void { function l10nGenerateXlf(paths: string[], language: string, outFile: string): void { console.log('Searching for L10N JSON files...'); - const matches = paths.map(p => glob.sync(p)).flat(); + const matches = paths.map(p => glob.sync(toPosixPath(p))).flat(); const l10nFileContents = matches.reduce>((prev, curr) => { if (curr.endsWith('.l10n.json')) { const name = path.basename(curr).split('.l10n.json')[0] ?? ''; @@ -127,7 +128,7 @@ function l10nGenerateXlf(paths: string[], language: string, outFile: string): vo prev.set('package', JSON.parse(readFileSync(path.resolve(curr), 'utf8'))); return prev; } - const results = glob.sync(path.join(curr, `{,!(node_modules)/**}`, '{*.l10n.json,package.nls.json}')); + const results = glob.sync(path.posix.join(curr, `{,!(node_modules)/**}`, '{*.l10n.json,package.nls.json}')); for (const result of results) { if (result.endsWith('.l10n.json')) { const name = path.basename(curr).split('.l10n.json')[0] ?? ''; @@ -155,12 +156,12 @@ function l10nGenerateXlf(paths: string[], language: string, outFile: string): vo async function l10nImportXlf(paths: string[], outDir: string): Promise { console.log('Searching for XLF files...'); - const matches = paths.map(p => glob.sync(p)).flat(); + const matches = paths.map(p => glob.sync(toPosixPath(p))).flat(); const xlfFiles = matches.reduce((prev, curr) => { if (curr.endsWith('.xlf')) { prev.push(readFileSync(path.resolve(curr), 'utf8')); } - const results = glob.sync(path.join(curr, `{,!(node_modules)/**}`, '*.xlf')); + const results = glob.sync(path.posix.join(curr, `{,!(node_modules)/**}`, '*.xlf')); for (const result of results) { prev.push(readFileSync(path.resolve(result), 'utf8')); } @@ -189,3 +190,7 @@ async function l10nImportXlf(paths: string[], outDir: string): Promise { } console.log(`Wrote ${count} localized L10N JSON files to: ${outDir}`); } + +function toPosixPath(pathToConvert: string): string { + return pathToConvert.split(path.win32.sep).join(path.posix.sep); +} diff --git a/l10n-dev/src/xlf/test/xlf.test.ts b/l10n-dev/src/xlf/test/xlf.test.ts index 2c642a8..0fa47e4 100644 --- a/l10n-dev/src/xlf/test/xlf.test.ts +++ b/l10n-dev/src/xlf/test/xlf.test.ts @@ -14,11 +14,26 @@ describe('XLF', () => { assert.strictEqual(result, '\r\n\r\n \r\n \r\n Hello\r\n \r\n \r\n'); }); - it('escapes double quotes', () => { + + it('escapes things correctly', () => { const xlf = new XLF(); - xlf.addFile('bundle', { '""': 'Hello' }); + // Use to of each to ensure we don't accidentally just grab the first one + xlf.addFile('bundle', { + '""': 'quotes', + "''": 'apostrophes', + '<<': 'less than', + '>>': 'greater than', + '&&': 'ampersands' + }); const result = xlf.toString(); - assert.strictEqual(result, '\r\n\r\n \r\n \r\n Hello\r\n \r\n \r\n'); + const header = '\r\n\r\n '; + const quotes = '\r\n \r\n quotes\r\n '; + const apostrophes = '\r\n \r\n apostrophes\r\n '; + const lessThan = '\r\n \r\n less than\r\n '; + const greaterThan = '\r\n \r\n greater than\r\n '; + const amp = '\r\n \r\n ampersands\r\n '; + const footer = '\r\n \r\n'; + assert.strictEqual(result, header + quotes + apostrophes + lessThan + greaterThan + amp + footer); }); it('parses double quotes correctly', async () => { @@ -28,8 +43,24 @@ describe('XLF', () => { - Hello - World + "" + "" + + + '' + '' + + + << + << + + + >> + >> + + + && + && @@ -40,6 +71,14 @@ describe('XLF', () => { assert.strictEqual(result[0]!.language, 'de'); assert.strictEqual(result[0]!.name, 'package'); assert.ok(result[0]!.messages['""']); - assert.strictEqual(result[0]!.messages['""'], 'World'); + assert.strictEqual(result[0]!.messages['""'], '""'); + assert.ok(result[0]!.messages["''"]); + assert.strictEqual(result[0]!.messages["''"], "''"); + assert.ok(result[0]!.messages['<<']); + assert.strictEqual(result[0]!.messages['<<'], '<<'); + assert.ok(result[0]!.messages['>>']); + assert.strictEqual(result[0]!.messages['>>'], '>>'); + assert.ok(result[0]!.messages['&&']); + assert.strictEqual(result[0]!.messages['&&'], '&&'); }); }); diff --git a/l10n-dev/src/xlf/xlf.ts b/l10n-dev/src/xlf/xlf.ts index f5b90c2..ede4e43 100644 --- a/l10n-dev/src/xlf/xlf.ts +++ b/l10n-dev/src/xlf/xlf.ts @@ -70,7 +70,7 @@ export class XLF { throw new Error('No item ID or value specified.'); } - this.appendNewLine(``, 4); + this.appendNewLine(``, 4); this.appendNewLine(`${item.message}`, 6); if (item.comment) { @@ -165,6 +165,12 @@ function encodeEntities(value: string): string { for (let i = 0; i < value.length; i++) { const ch = value[i]!; switch (ch) { + case '"': + result.push('"'); + break; + case "'": + result.push('''); + break; case '<': result.push('<'); break; @@ -182,5 +188,10 @@ function encodeEntities(value: string): string { } function decodeEntities(value: string): string { - return value.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); + return value + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&'); }