Skip to content

Commit

Permalink
Create localization of strings (#2757)
Browse files Browse the repository at this point in the history
Creation of a new module - localize. 
This doesn't use the vscode-nls and vscode-nls-dev as they
interfere with our code coverage.
Adding new strings requires adding them to the localize.ts and the
localize.nls.<locale>.json files.
Future work would be to translate the nls.json files into xlf files for
the loc team to own. We'd then write a build time task to turn the
xlf files into the output nls.<locale>.json files so that this code
works the same.

For #463
  • Loading branch information
rchiodo authored and DonJayamanne committed Oct 9, 2018
1 parent 2d0a09d commit 027832d
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 29 deletions.
36 changes: 18 additions & 18 deletions coverconfig.json
@@ -1,18 +1,18 @@
{
"enabled": false,
"relativeSourcePath": "../client",
"relativeCoverageDir": "../../coverage",
"ignorePatterns": [
"**/node_modules/**"
],
"reports": [
"text-summary",
"json-summary",
"json",
"html",
"lcov",
"lcovonly",
"cobertura"
],
"verbose": false
}
{
"enabled": false,
"relativeSourcePath": "../client",
"relativeCoverageDir": "../../coverage",
"ignorePatterns": [
"**/node_modules/**"
],
"reports": [
"text-summary",
"json-summary",
"json",
"html",
"lcov",
"lcovonly",
"cobertura"
],
"verbose": false
}
1 change: 1 addition & 0 deletions news/1 Enhancements/463.md
@@ -0,0 +1 @@
Add localization of strings. Localized versions are specified in the package.nls.\<locale\>.json files.
28 changes: 21 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.nls.it.json
Expand Up @@ -45,5 +45,6 @@
"python.snippet.launch.attach.label": "Python: Allega",
"python.snippet.launch.attach.description": "Allega debugger per debug remoto",
"python.snippet.launch.scrapy.label": "Python: Scrapy",
"python.snippet.launch.scrapy.description": "Scrapy con terminale integrato"
"python.snippet.launch.scrapy.description": "Scrapy con terminale integrato",
"LanguageServiceSurveyBanner.bannerLabelYes": "Sì, prenderò il sondaggio ora"
}
5 changes: 4 additions & 1 deletion package.nls.json
Expand Up @@ -48,5 +48,8 @@
"python.snippet.launch.attach.label": "Python: Attach",
"python.snippet.launch.attach.description": "Attach the Debugger for Remote Debugging",
"python.snippet.launch.scrapy.label": "Python: Scrapy",
"python.snippet.launch.scrapy.description": "Scrapy with Integrated Terminal/Console"
"python.snippet.launch.scrapy.description": "Scrapy with Integrated Terminal/Console",
"LanguageServiceSurveyBanner.bannerMessage": "Can you please take 2 minutes to tell us how the Python Language Server is working for you?",
"LanguageServiceSurveyBanner.bannerLabelYes": "Yes, take survey now",
"LanguageServiceSurveyBanner.bannerLabelNo": "No, thanks"
}
5 changes: 3 additions & 2 deletions src/client/languageServices/languageServerSurveyBanner.ts
Expand Up @@ -4,6 +4,7 @@
'use strict';

import { inject, injectable } from 'inversify';
import * as localize from '../../utils/localize';
import { getRandomBetween } from '../../utils/random';
import { FolderVersionPair, ILanguageServerFolderService } from '../activation/types';
import { IApplicationShell } from '../common/application/types';
Expand Down Expand Up @@ -35,8 +36,8 @@ export class LanguageServerSurveyBanner implements IPythonExtensionBanner {
private minCompletionsBeforeShow: number;
private maxCompletionsBeforeShow: number;
private isInitialized: boolean = false;
private bannerMessage: string = 'Can you please take 2 minutes to tell us how the Python Language Server is working for you?';
private bannerLabels: string[] = ['Yes, take survey now', 'No, thanks'];
private bannerMessage: string = localize.LanguageServiceSurveyBanner.bannerMessage();
private bannerLabels: string[] = [localize.LanguageServiceSurveyBanner.bannerLabelYes(), localize.LanguageServiceSurveyBanner.bannerLabelNo()];

constructor(
@inject(IApplicationShell) private appShell: IApplicationShell,
Expand Down
26 changes: 26 additions & 0 deletions src/test/common/localize.unit.test.ts
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import * as assert from 'assert';
import * as localize from '../../utils/localize';

// Defines a Mocha test suite to group tests of similar kind together
suite('localize tests', () => {

test('keys', done => {
const val = localize.LanguageServiceSurveyBanner.bannerMessage();
assert.equal(val, 'Can you please take 2 minutes to tell us how the Python Language Server is working for you?', 'LanguageServiceSurveyBanner string doesnt match');
done();
});

test('keys italian', done => {
// Force a config change
process.env.VSCODE_NLS_CONFIG = '{ "locale": "it" }';

const val = localize.LanguageServiceSurveyBanner.bannerLabelYes();
assert.equal(val, 'Sì, prenderò il sondaggio ora', 'bannerLabelYes is not being translated');
done();
});
});
83 changes: 83 additions & 0 deletions src/utils/localize.ts
@@ -0,0 +1,83 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import * as fs from 'fs';
import * as path from 'path';
import { EXTENSION_ROOT_DIR } from '../client/common/constants';

// External callers of localize use these tables to retrieve localized values.
export namespace LanguageServiceSurveyBanner {
export const bannerMessage = localize('LanguageServiceSurveyBanner.bannerMessage', 'Can you please take 2 minutes to tell us how the Python Language Server is working for you?');
export const bannerLabelYes = localize('LanguageServiceSurveyBanner.bannerLabelYes', 'Yes, take survey now');
export const bannerLabelNo = localize('LanguageServiceSurveyBanner.bannerLabelNo', 'No, thanks');
}

// Skip using vscode-nls and instead just compute our strings based on key values. Key values
// can be loaded out of the nls.<locale>.json files
let loadedCollection: { [index: string]: string } | undefined ;
let defaultCollection: { [index: string]: string } | undefined ;
let loadedLocale: string;

function localize(key: string, defValue: string) {
// Return a pointer to function so that we refetch it on each call.
return () => {
return getString(key, defValue);
};
}

function parseLocale() : string {
// Attempt to load from the vscode locale. If not there, use english
const vscodeConfigString = process.env.VSCODE_NLS_CONFIG;
return vscodeConfigString ? JSON.parse(vscodeConfigString).locale : 'en-us';
}

function getString(key: string, defValue: string) {
// Load the current collection
if (!loadedCollection || parseLocale() !== loadedLocale) {
load();
}

// First lookup in the dictionary that matches the current locale
if (loadedCollection && loadedCollection.hasOwnProperty(key)) {
return loadedCollection[key];
}

// Fallback to the default dictionary
if (defaultCollection && defaultCollection.hasOwnProperty(key)) {
return defaultCollection[key];
}

// Not found, return the default
return defValue;
}

function load() {
// Figure out our current locale.
loadedLocale = parseLocale();

// Find the nls file that matches (if there is one)
const nlsFile = path.join(EXTENSION_ROOT_DIR, `package.nls.${loadedLocale}.json`);
if (fs.existsSync(nlsFile)) {
const contents = fs.readFileSync(nlsFile, 'utf8');
loadedCollection = JSON.parse(contents);
} else {
// If there isn't one, at least remember that we looked so we don't try to load a second time
loadedCollection = {};
}

// Get the default collection if necessary. Strings may be in the default or the locale json
if (!defaultCollection) {
const defaultNlsFile = path.join(EXTENSION_ROOT_DIR, 'package.nls.json');
if (fs.existsSync(defaultNlsFile)) {
const contents = fs.readFileSync(defaultNlsFile, 'utf8');
return JSON.parse(contents);
} else {
defaultCollection = {};
}
}
}

// Default to loading the current locale
load();

0 comments on commit 027832d

Please sign in to comment.