diff --git a/src/browser/pages/vscode.ts b/src/browser/pages/vscode.ts index d95e9174..4f6191e4 100644 --- a/src/browser/pages/vscode.ts +++ b/src/browser/pages/vscode.ts @@ -5,9 +5,56 @@ const options = getOptions() // TODO: Add proper types. /* eslint-disable @typescript-eslint/no-explicit-any */ -let nlsConfig: any +// NOTE@jsjoeio +// This lives here ../../../lib/vscode/src/vs/base/common/platform.ts#L106 +export const nlsConfigElementId = "vscode-remote-nls-configuration" + +type NlsConfiguration = { + locale: string + availableLanguages: { [key: string]: string } | {} + _languagePackId?: string + _translationsConfigFile?: string + _cacheRoot?: string + _resolvedLanguagePackCoreLocation?: string + _corruptedFile?: string + _languagePackSupport?: boolean + loadBundle?: any +} + +/** + * A helper function to get the NLS Configuration settings. + * + * This is used by VSCode for localizations (i.e. changing + * the display language). + * + * Make sure to wrap this in a try/catch block when you call it. + **/ +export function getNlsConfiguration(document: Document) { + const errorMsgPrefix = "[vscode]" + const nlsConfigElement = document?.getElementById(nlsConfigElementId) + const nlsConfig = nlsConfigElement?.getAttribute("data-settings") + + if (!document) { + throw new Error(`${errorMsgPrefix} Could not parse NLS configuration. document is undefined.`) + } + + if (!nlsConfigElement) { + throw new Error( + `${errorMsgPrefix} Could not parse NLS configuration. Could not find nlsConfigElement with id: ${nlsConfigElementId}`, + ) + } + + if (!nlsConfig) { + throw new Error( + `${errorMsgPrefix} Could not parse NLS configuration. Found nlsConfigElement but missing data-settings attribute.`, + ) + } + + return JSON.parse(nlsConfig) as NlsConfiguration +} + try { - nlsConfig = JSON.parse(document.getElementById("vscode-remote-nls-configuration")!.getAttribute("data-settings")!) + const nlsConfig = getNlsConfiguration(document) if (nlsConfig._resolvedLanguagePackCoreLocation) { const bundles = Object.create(null) nlsConfig.loadBundle = (bundle: any, _language: any, cb: any): void => { @@ -26,26 +73,25 @@ try { .catch(cb) } } + ;(self.require as any) = { + // Without the full URL VS Code will try to load file://. + baseUrl: `${window.location.origin}${options.csStaticBase}/lib/vscode/out`, + recordStats: true, + paths: { + "vscode-textmate": `../node_modules/vscode-textmate/release/main`, + "vscode-oniguruma": `../node_modules/vscode-oniguruma/release/main`, + xterm: `../node_modules/xterm/lib/xterm.js`, + "xterm-addon-search": `../node_modules/xterm-addon-search/lib/xterm-addon-search.js`, + "xterm-addon-unicode11": `../node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`, + "xterm-addon-webgl": `../node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`, + "tas-client-umd": `../node_modules/tas-client-umd/lib/tas-client-umd.js`, + "iconv-lite-umd": `../node_modules/iconv-lite-umd/lib/iconv-lite-umd.js`, + jschardet: `../node_modules/jschardet/dist/jschardet.min.js`, + }, + "vs/nls": nlsConfig, + } } catch (error) { - /* Probably fine. */ -} - -;(self.require as any) = { - // Without the full URL VS Code will try to load file://. - baseUrl: `${window.location.origin}${options.csStaticBase}/lib/vscode/out`, - recordStats: true, - paths: { - "vscode-textmate": `../node_modules/vscode-textmate/release/main`, - "vscode-oniguruma": `../node_modules/vscode-oniguruma/release/main`, - xterm: `../node_modules/xterm/lib/xterm.js`, - "xterm-addon-search": `../node_modules/xterm-addon-search/lib/xterm-addon-search.js`, - "xterm-addon-unicode11": `../node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`, - "xterm-addon-webgl": `../node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`, - "tas-client-umd": `../node_modules/tas-client-umd/lib/tas-client-umd.js`, - "iconv-lite-umd": `../node_modules/iconv-lite-umd/lib/iconv-lite-umd.js`, - jschardet: `../node_modules/jschardet/dist/jschardet.min.js`, - }, - "vs/nls": nlsConfig, + console.error(error) } try { diff --git a/test/unit/browser/vscode.test.ts b/test/unit/browser/vscode.test.ts new file mode 100644 index 00000000..80246c7d --- /dev/null +++ b/test/unit/browser/vscode.test.ts @@ -0,0 +1,61 @@ +/** + * @jest-environment jsdom + */ +import { JSDOM } from "jsdom" +import { getNlsConfiguration, nlsConfigElementId } from "../../../src/browser/pages/vscode" + +describe("vscode", () => { + describe("getNlsConfiguration", () => { + beforeEach(() => { + const { window } = new JSDOM() + global.document = window.document + }) + + it("should throw an error if Document is undefined", () => { + const errorMsgPrefix = "[vscode]" + const errorMessage = `${errorMsgPrefix} Could not parse NLS configuration. document is undefined.` + + expect(() => { + getNlsConfiguration(undefined as any as Document) + }).toThrowError(errorMessage) + }) + it("should throw an error if no nlsConfigElement", () => { + const errorMsgPrefix = "[vscode]" + const errorMessage = `${errorMsgPrefix} Could not parse NLS configuration. Could not find nlsConfigElement with id: ${nlsConfigElementId}` + + expect(() => { + getNlsConfiguration(document) + }).toThrowError(errorMessage) + }) + it("should throw an error if no nlsConfig", () => { + const mockElement = document.createElement("div") + mockElement.setAttribute("id", nlsConfigElementId) + document.body.appendChild(mockElement) + + const errorMsgPrefix = "[vscode]" + const errorMessage = `${errorMsgPrefix} Could not parse NLS configuration. Found nlsConfigElement but missing data-settings attribute.` + + expect(() => { + getNlsConfiguration(document) + }).toThrowError(errorMessage) + + document.body.removeChild(mockElement) + }) + it("should return the correct configuration", () => { + const mockElement = document.createElement("div") + const dataSettings = { + first: "Jane", + last: "Doe", + } + + mockElement.setAttribute("id", nlsConfigElementId) + mockElement.setAttribute("data-settings", JSON.stringify(dataSettings)) + document.body.appendChild(mockElement) + const actual = getNlsConfiguration(global.document) + + expect(actual).toStrictEqual(dataSettings) + + document.body.removeChild(mockElement) + }) + }) +})