Skip to content

Commit

Permalink
Closes #577 - adds i18n support
Browse files Browse the repository at this point in the history
  • Loading branch information
airaketa committed Apr 29, 2022
1 parent bf8cd8f commit 5568f2e
Show file tree
Hide file tree
Showing 103 changed files with 6,310 additions and 2,695 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ node_modules
images/settings
gitlens-*.vsix
tsconfig*.tsbuildinfo
vscode-gitlens-localization-export
vscode-translations-import
4 changes: 4 additions & 0 deletions .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ tsconfig*.json
tsconfig*.tsbuildinfo
webpack.config*.js
yarn.lock
i18n/**
vscode-gitlens-localization-export/**
vscode-translations-import/**
gulpfile.js
48 changes: 48 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,51 @@ Please follow all the instructions in the [PR template](.github/PULL_REQUEST_TEM
This repository contains both OSS-licensed and non-OSS-licensed files. All files in or under any directory named "plus" fall under LICENSE.plus. The remaining files fall under LICENSE, the MIT license.

If a pull request is submitted which contains changes to files in or under any directory named "plus", then you agree that GitKraken and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications and/or patches.

### String Localization

[vscode-nls](https://github.com/microsoft/vscode-nls) is used to localize strings in TypeScript code. To use [vscode-nls](https://github.com/microsoft/vscode-nls), the source file must contain:

```typescript
import * as nls from 'vscode-nls';

nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
```

For each user-facing string, wrap the string in a call to localize:

```typescript
const fetchReposMessage: string = localize(
'quickPick.fetch.detail.willFetchRepos',
'Will fetch {0}',
reposToFetch
);
```

The first parameter to localize should be a unique key for that string, not used by any other call to localize() in the file unless representing the same string. The second parameter is the string to localize. Both of these parameters must be string literals. Tokens such as {0} and {1} are supported in the localizable string, with replacement values passed as additional parameters to localize().

#### Adding a New Language
[Gulp](https://gulpjs.com/) is used to automate localized strings import and export.
To add new language support one should follow these steps:
1. Add target language to `gulpfile.js`.

For example:
```javascript
const languages = [
{ id: 'ru', folderName: 'rus' },
];
```

2. Export internalized strings into `.xlf` file by calling `gulp translations-export` from a terminal
You can find the resulting file under `vscode-gitlens-localization-export/vscode-gitlens/gitlens.xlf`

3. Translate the .xlf file using localization tool of your choice. (e.g. [Transifex](https://www.transifex.com/) or [Smartcat](https://smartcat.com))

4. Put localized file under `vscode-translations-import/<target language ID>/vscode-gitlens/gitlens.xlf`

5. Import localizations by calling `gulp translations-import`

6. You can find imported localizations under `i18n/<target language folderName>`

7. Now you can build project and test your localization in work
156 changes: 156 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

'use strict';

const gulp = require('gulp');
const nls = require('vscode-nls-dev');
const path = require('path');
const minimist = require('minimist');
const es = require('event-stream');
const sourcemaps = require('gulp-sourcemaps');
const ts = require('gulp-typescript');
const typescript = require('typescript');
const tsProject = ts.createProject('./tsconfig.json', { typescript, rootDir: '.' });
tsProject.base = '/home/airaketa/vscode-gitlens';
const filter = require('gulp-filter');

const languages = [
// { id: "zh-tw", folderName: "cht", transifexId: "zh-hant" },
// { id: "zh-cn", folderName: "chs", transifexId: "zh-hans" },
// { id: "fr", folderName: "fra" },
// { id: "de", folderName: "deu" },
// { id: "it", folderName: "ita" },
// { id: "es", folderName: "esn" },
// { id: "ja", folderName: "jpn" },
// { id: "ko", folderName: "kor" },
// { id: 'ru', folderName: 'rus' },
//{ id: "bg", folderName: "bul" }, // VS Code supports Bulgarian, but VS is not currently localized for it
//{ id: "hu", folderName: "hun" }, // VS Code supports Hungarian, but VS is not currently localized for it
// { id: "pt-br", folderName: "ptb", transifexId: "pt-BR" },
// { id: "tr", folderName: "trk" },
// { id: "cs", folderName: "csy" },
// { id: "pl", folderName: "plk" }
];

// ****************************
// Command: translations-export
// The following is used to export and XLF file containing english strings for translations.
// The result will be written to: ../vscode-extensions-localization-export/ms-vscode/
// ****************************

const translationProjectName = 'vscode-gitlens';
const translationExtensionName = 'gitlens';

// descriptionCallback(path, value, parent) is invoked for attributes
const traverseJson = (jsonTree, descriptionCallback, prefixPath) => {
for (let fieldName in jsonTree) {
if (jsonTree[fieldName] !== null) {
if (
typeof jsonTree[fieldName] == 'string' &&
(fieldName === 'description' || fieldName === 'markdownDescription')
) {
descriptionCallback(prefixPath, jsonTree[fieldName], jsonTree);
} else if (typeof jsonTree[fieldName] == 'object') {
let path = prefixPath;
if (path !== '') path = path + '.';
path = path + fieldName;
traverseJson(jsonTree[fieldName], descriptionCallback, path);
}
}
}
};

gulp.task('translations-export', done => {
// Transpile the TS to JS, and let vscode-nls-dev scan the files for calls to localize.
tsProject
.src()
.pipe(sourcemaps.init())
.pipe(tsProject())
.js.pipe(nls.createMetaDataFiles())

// Filter down to only the files we need
.pipe(filter(['**/*.nls.json', '**/*.nls.metadata.json']))

// Consoldate them into nls.metadata.json, which the xlf is built from.
.pipe(nls.bundleMetaDataFiles('vscode.gitlens', '.'))

// filter down to just the resulting metadata files
.pipe(filter(['**/nls.metadata.header.json', '**/nls.metadata.json']))

// Add package.nls.json, used to localized package.json
.pipe(gulp.src(['package.nls.json']))

// package.nls.json and nls.metadata.json are used to generate the xlf file
// Does not re-queue any files to the stream. Outputs only the XLF file
.pipe(nls.createXlfFiles(translationProjectName, translationExtensionName))
.pipe(gulp.dest(`${translationProjectName}-localization-export`))
.pipe(
es.wait(() => {
done();
}),
);
});

// ****************************
// Command: translations-import
// The following is used to import an XLF file containing all language strings.
// This results in a i18n directory, which should be checked in.
// ****************************

// Imports translations from raw localized MLCP strings to VS Code .i18n.json files
gulp.task('translations-import', done => {
let options = minimist(process.argv.slice(2), {
string: 'location',
default: {
location: 'vscode-translations-import',
},
});
es.merge(
languages.map(language => {
let id = language.transifexId || language.id;
return gulp
.src(path.join(options.location, id, translationProjectName, `${translationExtensionName}.xlf`))
.pipe(nls.prepareJsonFiles())
.pipe(gulp.dest(path.join('./i18n', language.folderName)));
}),
).pipe(
es.wait(() => {
done();
}),
);
});

// ****************************
// Command: translations-generate
// The following is used to import an i18n directory structure and generate files used at runtime.
// ****************************

// Generate package.nls.*.json files from: ./i18n/*/package.i18n.json
// Outputs to root path, as these nls files need to be along side package.json
const generateAdditionalLocFiles = () => {
return gulp
.src(['package.nls.json'])
.pipe(nls.createAdditionalLanguageFiles(languages, 'i18n'))
.pipe(gulp.dest('.'));
};

// Generates ./dist/nls.bundle.<language_id>.json from files in ./i18n/** *//<src_path>/<filename>.i18n.json
// Localized strings are read from these files at runtime.
const generateSrcLocBundle = () => {
// Transpile the TS to JS, and let vscode-nls-dev scan the files for calls to localize.
return tsProject
.src()
.pipe(sourcemaps.init())
.pipe(tsProject())
.js.pipe(nls.createMetaDataFiles())
.pipe(nls.createAdditionalLanguageFiles(languages, 'i18n'))
.pipe(nls.bundleMetaDataFiles('vscode-gitlens', 'dist'))
.pipe(nls.bundleLanguageFiles())
.pipe(filter(['**/nls.bundle.*.json', '**/nls.metadata.header.json', '**/nls.metadata.json']))
.pipe(gulp.dest('dist'));
};

gulp.task('translations-generate', gulp.series(generateSrcLocBundle, generateAdditionalLocFiles));

0 comments on commit 5568f2e

Please sign in to comment.