diff --git a/i18n-sample/.gitignore b/i18n-sample/.gitignore new file mode 100644 index 00000000..5c8f6865 --- /dev/null +++ b/i18n-sample/.gitignore @@ -0,0 +1,4 @@ +out +node_modules +// These files will be generated from the i18n folder +*nls.*.json diff --git a/i18n-sample/.vscode/launch.json b/i18n-sample/.vscode/launch.json new file mode 100644 index 00000000..30625e88 --- /dev/null +++ b/i18n-sample/.vscode/launch.json @@ -0,0 +1,17 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Launch Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ "${workspaceRoot}/out/src/**/*.js" ], + "preLaunchTask": "npm" + } + ] +} diff --git a/i18n-sample/.vscode/settings.json b/i18n-sample/.vscode/settings.json new file mode 100644 index 00000000..d1371333 --- /dev/null +++ b/i18n-sample/.vscode/settings.json @@ -0,0 +1,9 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + } +} \ No newline at end of file diff --git a/i18n-sample/.vscode/tasks.json b/i18n-sample/.vscode/tasks.json new file mode 100644 index 00000000..b684a876 --- /dev/null +++ b/i18n-sample/.vscode/tasks.json @@ -0,0 +1,27 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "isBackground": true, + "problemMatcher": [ + "$tsc-watch" + ] + }, + { + "type": "gulp", + "task": "clean", + "problemMatcher": [] + }, + { + "type": "gulp", + "task": "build", + "problemMatcher": [ + "$tsc" + ] + } + ] +} \ No newline at end of file diff --git a/i18n-sample/.vscodeignore b/i18n-sample/.vscodeignore new file mode 100644 index 00000000..5ff3c193 --- /dev/null +++ b/i18n-sample/.vscodeignore @@ -0,0 +1,9 @@ +.vscode/** +.vscode-test/** +out/test/** +test/** +src/** +**/*.map +.gitignore +tsconfig.json +vsc-extension-quickstart.md diff --git a/i18n-sample/README.md b/i18n-sample/README.md new file mode 100644 index 00000000..953ddfb4 --- /dev/null +++ b/i18n-sample/README.md @@ -0,0 +1,102 @@ +# README +## This is the README for the "i18n-sample" +------------------- + +This folder contains a sample VS code extension that shows how to use the +package.nls.json and the vscode-nls library for localization. For this sample, +it shows two commands: Hello and Bye in English and Japanese. + +**Assumptions** + +* All localization files are under the i18n folder. +* You could have created this folder by hand, or you could have used the `vscode-nls-dev` tool to extract it. +* Under the i18n folder, you have sub-folders that represent the language you want to localize. These names follow the ISO 639-3 convention. +* Under the language names folder you will create json files that mirror the structure of the source code for your extension (e.g., out/src). The json files are key:value pairs of the text that you want to localize. The naming convention is `.i18n.json`. +* If you have a top-level package.nls.json file in your extension, you should have one for each language following the naming convention of `package.i18n.json`. + +# How to run locally + +Localization values are only applied when running the gulp `build` task. During normally development which uses `tsc -watch` to compile no localization post processing happends. This speeds up development time. + +1. Ensure that you have `gulp-cli` installed globally using `npm install --global gulp-cli`. +1. Run `npm install` to bring in the dependencies. +1. Follow the steps at https://code.visualstudio.com/docs/extensions/publish-extension to ensure that you have installed vsce and have a publisher account. +1. Run `gulp package` to produce a .vsix file. +1. Install the .vsix file following the instructions at https://code.visualstudio.com/docs/editor/extension-gallery#_install-from-a-vsix +1. Change your locale to Japanese by invoking "Configure Language" from the Command Palette. + +See the demo.gif file in this repository for a screencast. + +# How to translate our extension + +VS Code itself uses [Transifex](https://www.transifex.com/) to manage its translations. This might be an option for your extension as well, however none of the nls tooling provided by `vscode-nls` or `vscode-nls-dev` requires Transifex as its translation platform. So you are free to choose a different one. + +# What happens behind the scenes + +1. The `vscode-nls-dev` module is used to rewrite the generated JavaScript. +1. Calls of the form `localize('some_key', 'Hello')` are transformed to `localize(0, null)` where the first parameter (0, in this example) is the position of the key in your messages file. +1. The contents of the i18n folder are transformed from key:value pairs into positional arrays. + +# Considerations + +It is possible to use your own localization pipeline. + +1. Localizations in your package.json can be done by wrapping the localized text in the form %some.key%. + +``` +// [Before] package.json + + "contributes": { + "commands": [ + { + "command": "extension.sayHello", + "title": "Hello" + } + ] + +// [After] package.json + + "contributes": { + "commands": [ + { + "command": "extension.sayHello", + "title": "%extension.sayHello.title%" + } + ] + +// [After] new package.nls.json + +{ + "extension.sayHello.title": "Hello", +} + +``` + +Then, create the corresponding package.nls.{your_language}.json files for each language to localize. + +2. It is also possible to use your own library for localizing text in your source file. You would use the value of `process.env.VSCODE_NLS_CONFIG` environment variable. At runtime, this environment variable is a JSON string that contains the locale that VS Code is run with. For instance, this is the value for Japanese: `"{"locale":"ja","availableLanguages":{"*":"ja"}}"` + +```JavaScript + +function localize(config) { + const messages = { + en: 'Hello', + ja: 'こんにちは' + }; + return messages[config['locale']]; +} + +const config = JSON.parse(process.env.VSCODE_NLS_CONFIG); +localize(config); + +``` + +# History + +## 0.0.2 + +Hook up the vscode-nls-dev functions to gulp so that you can just run `vsce package` without manual transformations. + +## 0.0.1: + +Manually transform the calls to localize to illustrate explicitly what is going on. diff --git a/i18n-sample/demo.gif b/i18n-sample/demo.gif new file mode 100644 index 00000000..21c12edf Binary files /dev/null and b/i18n-sample/demo.gif differ diff --git a/i18n-sample/gulpfile.js b/i18n-sample/gulpfile.js new file mode 100644 index 00000000..1dbd87f3 --- /dev/null +++ b/i18n-sample/gulpfile.js @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const gulp = require('gulp'); +const path = require('path'); + +const ts = require('gulp-typescript'); +const typescript = require('typescript'); +const sourcemaps = require('gulp-sourcemaps'); +const del = require('del'); +const runSequence = require('run-sequence'); +const es = require('event-stream'); +const vsce = require('vsce'); +const nls = require('vscode-nls-dev'); + +const tsProject = ts.createProject('./tsconfig.json', { typescript }); + +const inlineMap = true; +const inlineSource = false; +const outDest = 'out'; + +// If all VS Code langaues are support you can use nls.coreLanguages +const languages = ['jpn']; + +gulp.task('default', function(callback) { + runSequence('build', callback); +}); + +gulp.task('compile', function(callback) { + runSequence('clean', 'internal-compile', callback); +}); + +gulp.task('build', function(callback) { + runSequence('clean', 'internal-nls-compile', 'add-i18n', callback); +}); + +gulp.task('publish', function(callback) { + runSequence('build', 'vsce:publish', callback); +}); + +gulp.task('package', function(callback) { + runSequence('build', 'vsce:package', callback); +}); + +gulp.task('clean', function() { + return del(['out/**', 'package.nls.*.json', 'i18n-sample*.vsix']); +}) + +//---- internal + +function compile(buildNls) { + var r = tsProject.src() + .pipe(sourcemaps.init()) + .pipe(tsProject()).js + .pipe(buildNls ? nls.rewriteLocalizeCalls() : es.through()) + .pipe(buildNls ? nls.createAdditionalLanguageFiles(languages, 'i18n', 'out') : es.through()); + + if (inlineMap && inlineSource) { + r = r.pipe(sourcemaps.write()); + } else { + r = r.pipe(sourcemaps.write("../out", { + // no inlined source + includeContent: inlineSource, + // Return relative source map root directories per file. + sourceRoot: "../src" + })); + } + + return r.pipe(gulp.dest(outDest)); +} + +gulp.task('internal-compile', function() { + return compile(false); +}); + +gulp.task('internal-nls-compile', function() { + return compile(true); +}); + +gulp.task('add-i18n', function() { + return gulp.src(['package.nls.json']) + .pipe(nls.createAdditionalLanguageFiles(languages, 'i18n')) + .pipe(gulp.dest('.')); +}); + +gulp.task('vsce:publish', function() { + return vsce.publish(); +}); + +gulp.task('vsce:package', function() { + return vsce.createVSIX(); +}); diff --git a/i18n-sample/i18n/jpn/out/command/sayBye.i18n.json b/i18n-sample/i18n/jpn/out/command/sayBye.i18n.json new file mode 100644 index 00000000..ca3da972 --- /dev/null +++ b/i18n-sample/i18n/jpn/out/command/sayBye.i18n.json @@ -0,0 +1,3 @@ +{ + "sayBye.text": "さようなら" +} \ No newline at end of file diff --git a/i18n-sample/i18n/jpn/out/extension.i18n.json b/i18n-sample/i18n/jpn/out/extension.i18n.json new file mode 100644 index 00000000..2c696dfa --- /dev/null +++ b/i18n-sample/i18n/jpn/out/extension.i18n.json @@ -0,0 +1,3 @@ +{ + "sayHello.text": "こんにちは" +} \ No newline at end of file diff --git a/i18n-sample/i18n/jpn/package.i18n.json b/i18n-sample/i18n/jpn/package.i18n.json new file mode 100644 index 00000000..21ddd66d --- /dev/null +++ b/i18n-sample/i18n/jpn/package.i18n.json @@ -0,0 +1,4 @@ +{ + "extension.sayHello.title": "こんにちは", + "extension.sayBye.title": "さようなら" +} diff --git a/i18n-sample/package.json b/i18n-sample/package.json new file mode 100644 index 00000000..4036ae38 --- /dev/null +++ b/i18n-sample/package.json @@ -0,0 +1,51 @@ +{ + "name": "i18n-sample", + "displayName": "i18n-sample", + "description": "Sample that shows how to localize an extension", + "version": "0.1.0", + "publisher": "vazexqi", + "engines": { + "vscode": "^1.13.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onCommand:extension.sayHello", + "onCommand:extension.sayBye" + ], + "main": "./out/extension", + "contributes": { + "commands": [ + { + "command": "extension.sayHello", + "title": "%extension.sayHello.title%" + }, + { + "command": "extension.sayBye", + "title": "%extension.sayBye.title%" + } + ] + }, + "scripts": { + "watch": "tsc -watch -p ./", + "clean": "gulp clean", + "postinstall": "node ./node_modules/vscode/bin/install" + }, + "devDependencies": { + "@types/node": "^6.0.40", + "del": "^3.0.0", + "event-stream": "^3.3.4", + "gulp": "^3.9.1", + "gulp-filter": "^5.0.1", + "run-sequence": "^2.2.0", + "typescript": "2.5.3", + "gulp-typescript": "3.2.2", + "vsce": "^1.32.0", + "vscode": "^1.1.2", + "vscode-nls-dev": "^2.1.3" + }, + "dependencies": { + "vscode-nls": "^2.0.2" + } +} \ No newline at end of file diff --git a/i18n-sample/package.nls.json b/i18n-sample/package.nls.json new file mode 100644 index 00000000..3007d462 --- /dev/null +++ b/i18n-sample/package.nls.json @@ -0,0 +1,4 @@ +{ + "extension.sayHello.title": "Hello", + "extension.sayBye.title": "Bye" +} \ No newline at end of file diff --git a/i18n-sample/src/command/sayBye.ts b/i18n-sample/src/command/sayBye.ts new file mode 100644 index 00000000..5c9a636f --- /dev/null +++ b/i18n-sample/src/command/sayBye.ts @@ -0,0 +1,14 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); + +export function sayByeCommand() { + const message = localize('sayBye.text', 'Bye') + vscode.window.showInformationMessage(message); +} \ No newline at end of file diff --git a/i18n-sample/src/extension.ts b/i18n-sample/src/extension.ts new file mode 100644 index 00000000..c48a15b4 --- /dev/null +++ b/i18n-sample/src/extension.ts @@ -0,0 +1,27 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as nls from 'vscode-nls'; +const localize = nls.config(process.env.VSCODE_NLS_CONFIG)(); + +import * as vscode from 'vscode'; +import { sayByeCommand } from './command/sayBye'; + +export function activate(context: vscode.ExtensionContext) { + const helloCmd = vscode.commands.registerCommand('extension.sayHello', () => { + const message = localize('sayHello.text', 'Hello') + vscode.window.showInformationMessage(message); + }); + + const byeCmd = vscode.commands.registerCommand( + 'extension.sayBye', + sayByeCommand + ); + + context.subscriptions.push(helloCmd, byeCmd); +} + +export function deactivate() { +} diff --git a/i18n-sample/tsconfig.json b/i18n-sample/tsconfig.json new file mode 100644 index 00000000..afe5896a --- /dev/null +++ b/i18n-sample/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ + "es6" + ], + "sourceMap": true, + "rootDir": "src" + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file