Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Localize strings on github.dev using VSCode FS API #17711

Merged
merged 10 commits into from
Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/2 Fixes/17711.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Change localization in the extension to be async and use the VS Code APIs.
3 changes: 2 additions & 1 deletion src/client/browser/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { LanguageClientMiddlewareBase } from '../activation/languageClientMiddle
import { ILSExtensionApi } from '../activation/node/languageServerFolderService';
import { LanguageServerType } from '../activation/types';
import { AppinsightsKey, PVSC_EXTENSION_ID, PYLANCE_EXTENSION_ID } from '../common/constants';
import { loadLocalizedStrings } from '../common/utils/localize';
import { EventName } from '../telemetry/constants';

interface BrowserConfig {
Expand All @@ -17,7 +18,7 @@ interface BrowserConfig {

export async function activate(context: vscode.ExtensionContext): Promise<void> {
// Run in a promise and return early so that VS Code can go activate Pylance.

await loadLocalizedStrings();
const pylanceExtension = vscode.extensions.getExtension<ILSExtensionApi>(PYLANCE_EXTENSION_ID);
if (pylanceExtension) {
runPylance(context, pylanceExtension);
Expand Down
71 changes: 30 additions & 41 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

'use strict';

import * as path from 'path';
// IMPORTANT: Do not import any node fs related modules here, as they do not work in browser.
import * as vscode from 'vscode';
import { EXTENSION_ROOT_DIR } from '../../constants';
import { FileSystem } from '../platform/fileSystem';

/* eslint-disable @typescript-eslint/no-namespace, no-shadow */

Expand Down Expand Up @@ -568,36 +568,27 @@ export function _getAskedForCollection(): Record<string, string> {
return askedForCollection;
}

// Return the effective set of all localization strings, by key.
//
// This should not be used for direct lookup.
export function getCollectionJSON(): string {
// Load the current collection
if (!loadedCollection || parseLocale() !== loadedLocale) {
load();
}

// Combine the default and loaded collections
return JSON.stringify({ ...defaultCollection, ...loadedCollection });
}

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

declare let navigator: { language: string } | undefined;

function parseLocale(): string {
try {
if (navigator?.language) {
return navigator.language.toLowerCase();
}
} catch {
// Fall through
}
// 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();
}

// The default collection (package.nls.json) is the fallback.
// Note that we are guaranteed the following (during shipping)
// 1. defaultCollection was initialized by the load() call above
Expand All @@ -619,33 +610,31 @@ function getString(key: string, defValue?: string) {
return result;
}

function load() {
const fs = new FileSystem();

/**
* Only uses the VSCode APIs to query filesystem and not the node fs APIs, as
* they're not available in browser. Must be called before any use of the locale.
*/
export async function loadLocalizedStrings(): Promise<void> {
// 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.fileExistsSync(nlsFile)) {
const contents = fs.readFileSync(nlsFile);
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 = {};
}
loadedCollection = await parseNLS(loadedLocale);

// 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.fileExistsSync(defaultNlsFile)) {
const contents = fs.readFileSync(defaultNlsFile);
defaultCollection = JSON.parse(contents);
} else {
defaultCollection = {};
}
defaultCollection = await parseNLS();
}
}

// Default to loading the current locale
load();
async function parseNLS(locale?: string) {
try {
const filename = locale ? `package.nls.${locale}.json` : `package.nls.json`;
const nlsFile = vscode.Uri.joinPath(vscode.Uri.file(EXTENSION_ROOT_DIR), filename);
const buffer = await vscode.workspace.fs.readFile(nlsFile);
const contents = new TextDecoder().decode(buffer);
return JSON.parse(contents);
} catch {
// If there isn't one, at least remember that we looked so we don't try to load a second time.
return {};
}
}
3 changes: 2 additions & 1 deletion src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { IApplicationShell, IWorkspaceService } from './common/application/types
import { traceError } from './common/logger';
import { IAsyncDisposableRegistry, IExperimentService, IExtensionContext } from './common/types';
import { createDeferred } from './common/utils/async';
import { Common } from './common/utils/localize';
import { Common, loadLocalizedStrings } from './common/utils/localize';
import { activateComponents } from './extensionActivation';
import { initializeStandard, initializeComponents, initializeGlobals } from './extensionInit';
import { IServiceContainer } from './ioc/types';
Expand Down Expand Up @@ -99,6 +99,7 @@ async function activateUnsafe(
//===============================================
// activation starts here

await loadLocalizedStrings();
// First we initialize.
const ext = initializeGlobals(context);
activatedServiceContainer = ext.legacyIOC.serviceContainer;
Expand Down