diff --git a/build/pipeline.yml b/build/pipeline.yml index 24791000..9b92255c 100644 --- a/build/pipeline.yml +++ b/build/pipeline.yml @@ -23,6 +23,7 @@ parameters: extends: template: azure-pipelines/extension/stable.yml@templates parameters: + l10nSourcePaths: ./src buildSteps: - script: npm ci displayName: Install dependencies diff --git a/media/editor/copyPaste.tsx b/media/editor/copyPaste.tsx index 4145045c..e9b232fd 100644 --- a/media/editor/copyPaste.tsx +++ b/media/editor/copyPaste.tsx @@ -7,6 +7,7 @@ import { MessageType, PasteMode } from "../../shared/protocol"; import _style from "./copyPaste.css"; import { useUniqueId } from "./hooks"; import { messageHandler } from "./state"; +import { strings } from "./strings"; import { throwOnUndefinedAccessInDev } from "./util"; import { VsButton, VsWidgetPopover } from "./vscodeUi"; @@ -113,7 +114,7 @@ export const PastePopup: React.FC<{ return
- Paste as: + {strings.pasteAs}: {encodings.map(e =>
- Paste mode: - - + {strings.pasteMode}: + +
{decodedValid - ? <>{mode === PasteMode.Replace ? "Replace" : "Insert"} {decoded.length} bytes - : "Encoding Error"} + ? <>{mode === PasteMode.Replace ? strings.replace : strings.insert} {decoded.length} {strings.bytes} + : strings.encodingError}
; diff --git a/media/editor/dataDisplay.tsx b/media/editor/dataDisplay.tsx index 9857b636..fa6725cd 100644 --- a/media/editor/dataDisplay.tsx +++ b/media/editor/dataDisplay.tsx @@ -12,6 +12,7 @@ import { FocusedElement, dataCellCls, useDisplayContext, useIsFocused, useIsHove import { DataInspectorAside } from "./dataInspector"; import { useGlobalHandler, useLastAsyncRecoilValue } from "./hooks"; import * as select from "./state"; +import { strings } from "./strings"; import { clamp, clsx, getAsciiCharacter, getScrollDimensions, throwOnUndefinedAccessInDev } from "./util"; const style = throwOnUndefinedAccessInDev(_style); @@ -59,7 +60,7 @@ export const DataHeader: React.FC = () => { // Calculated decoded width so that the Data Inspector is displayed at the right position // Flex-shrink prevents the data inspector overlapping on narrow screens - Decoded Text + {strings.decodedText} )} {inspectorLocation === InspectorLocation.Aside && } @@ -294,7 +295,7 @@ const DataRows: React.FC = () => { const LoadingDataRow: React.FC<{ width: number; showDecodedText: boolean }> = ({ width, showDecodedText }) => { const cells: React.ReactNode[] = []; - const text = "LOADING"; + const text = strings.loadingUpper; for (let i = 0; i < width; i++) { const str = (text[i * 2] || ".") + (text[i * 2 + 1] || "."); cells.push( { return setInspected(undefined)} visible={true}> - + ; @@ -113,6 +114,6 @@ const EndiannessToggle: React.FC<{ checked={endianness === Endianness.Little} onChange={evt => setEndianness(evt.target.checked ? Endianness.Little : Endianness.Big)} /> - +
); diff --git a/media/editor/findWidget.tsx b/media/editor/findWidget.tsx index f306d095..8fa8256d 100644 --- a/media/editor/findWidget.tsx +++ b/media/editor/findWidget.tsx @@ -13,11 +13,13 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; import { useRecoilState, useRecoilValue } from "recoil"; import { HexDocumentEditOp, HexDocumentReplaceEdit } from "../../shared/hexDocumentModel"; import { LiteralSearchQuery, MessageType, SearchRequestMessage, SearchResult, SearchResultsWithProgress } from "../../shared/protocol"; +import { placeholder1 } from "../../shared/strings"; import { Range } from "../../shared/util/range"; import { FocusedElement, dataCellCls, useDisplayContext } from "./dataDisplayContext"; import _style from "./findWidget.css"; import { usePersistedState } from "./hooks"; import * as select from "./state"; +import { strings } from "./strings"; import { clsx, hexDecode, isHexString, parseHexDigit, throwOnUndefinedAccessInDev } from "./util"; import { VsIconButton, VsIconCheckbox, VsProgressIndicator, VsTextFieldGroup } from "./vscodeUi"; @@ -71,7 +73,7 @@ const getReplaceOrError = (replace: string, isBinaryMode: boolean) => { if (isBinaryMode) { return isHexString(replace) ? hexDecode(replace) - : "Only hexadecimal characters (0-9 and a-f) are allowed"; + : strings.onlyHexChars; } return new TextEncoder().encode(replace); @@ -79,7 +81,7 @@ const getReplaceOrError = (replace: string, isBinaryMode: boolean) => { const getSearchQueryOrError = (query: string, isBinaryMode: boolean, isRegexp: boolean): SearchRequestMessage["query"] | string => { if (isBinaryMode) { - return parseHexStringWithPlaceholders(query) || "Only hexadecimal characters (0-9, a-f, and ?? placeholders) are allowed"; + return parseHexStringWithPlaceholders(query) || strings.onlyHexCharsAndPlaceholders; } if (isRegexp) { @@ -322,33 +324,33 @@ export const FindWidget: React.FC = () => { buttons={3} ref={textFieldRef} outerClassName={style.textField} - placeholder={isBinaryMode ? "Find Bytes (hex)" : "Find Text"} + placeholder={isBinaryMode ? strings.findBytes : strings.findText} value={query} onChange={onQueryChange} onKeyDown={onFindKeyDown} error={typeof queryOrError === "string" ? queryOrError : undefined} > - {!isBinaryMode && + {!isBinaryMode && } - + - + setUncapped(true)} results={results} selectedResult={selectedResult} /> - + - navigateResults(-1)} title="Previous Match"> + navigateResults(-1)} title={strings.previousMatch}> - navigateResults(1)} title="Next Match"> + navigateResults(1)} title={strings.nextMatch}> - + @@ -359,13 +361,13 @@ export const FindWidget: React.FC = () => { value={replace} onChange={onReplaceChange} onKeyDown={onReplaceKeyDown} - placeholder="Replace" + placeholder={strings.replace} error={typeof replaceOrError === "string" ? replaceOrError : undefined} /> - + - + } @@ -381,14 +383,14 @@ const ResultBadge: React.FC<{ }> = ({ results, selectedResult, onUncap }) => { const resultCountStr = resultCountFormat.format(results.results.length); const resultCountComponent = results.capped - ? {resultCountStr}+ - : {resultCountStr}; + ? {resultCountStr}+ + : {resultCountStr}; return
{results.progress < 1 - ? `Found ${resultCountStr}...` + ? strings.foundNResults.replace(placeholder1, resultCountStr) : !results.results.length - ? "No results" + ? strings.noResults : selectedResult !== undefined ? <>{selectedFormat.format(selectedResult + 1)} of {resultCountComponent} : <>{resultCountComponent} results} diff --git a/media/editor/hexEdit.tsx b/media/editor/hexEdit.tsx index 96357e57..3ccef93f 100644 --- a/media/editor/hexEdit.tsx +++ b/media/editor/hexEdit.tsx @@ -15,6 +15,7 @@ import { ReadonlyWarning } from "./readonlyWarning"; import { ScrollContainer } from "./scrollContainer"; import { SettingsGear } from "./settings"; import * as select from "./state"; +import { strings } from "./strings"; import { throwOnUndefinedAccessInDev } from "./util"; import { VsProgressIndicator } from "./vscodeUi"; @@ -51,7 +52,7 @@ const Editor: React.FC = () => { if (isLargeFile && !bypassLargeFilePrompt) { return
-

Opening this large file may cause instability. setBypassLargeFile(true)}>Open anyways

+

{strings.openLargeFileWarning} setBypassLargeFile(true)}>{strings.openAnyways}

; } diff --git a/media/editor/readonlyWarning.tsx b/media/editor/readonlyWarning.tsx index f2eb7b60..87c457e8 100644 --- a/media/editor/readonlyWarning.tsx +++ b/media/editor/readonlyWarning.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useEffect } from "react"; import { useRecoilState } from "recoil"; import * as select from "./state"; +import { strings } from "./strings"; import { VsTooltipPopover } from "./vscodeUi"; export const ReadonlyWarning: React.FC = () => { @@ -17,6 +18,6 @@ export const ReadonlyWarning: React.FC = () => { }); return - Cannot edit in read-only editor + {strings.readonlyWarning} ; }; diff --git a/media/editor/settings.tsx b/media/editor/settings.tsx index 06c6f84e..cc740270 100644 --- a/media/editor/settings.tsx +++ b/media/editor/settings.tsx @@ -3,6 +3,7 @@ import React, { useState } from "react"; import { useRecoilState } from "recoil"; import _style from "./settings.css"; import * as select from "./state"; +import { strings } from "./strings"; import { throwOnUndefinedAccessInDev } from "./util"; import { VsIconButton, VsTextField, VsWidgetPopover } from "./vscodeUi"; @@ -14,7 +15,7 @@ export const SettingsGear: React.FC = () => { return ( <> - setIsOpen(!isOpen)} ref={setAnchor}> + setIsOpen(!isOpen)} ref={setAnchor}> setIsOpen(false)} visible={isOpen}> @@ -34,7 +35,7 @@ const TextCheckbox: React.FC = () => { return ( <> - + { return ( <> - +
diff --git a/src/goToOffset.ts b/src/goToOffset.ts index 4d5e56e8..e09df9f4 100644 --- a/src/goToOffset.ts +++ b/src/goToOffset.ts @@ -21,7 +21,7 @@ export const showGoToOffset = (messaging: ExtensionHostMessageHandler): void => } else if (decimalRe.test(value)) { lastValue = parseInt(value, 10); } else { - input.validationMessage = "Offset must be provided as a decimal (12345) or hex (0x12345) address"; + input.validationMessage = vscode.l10n.t("Offset must be provided as a decimal (12345) or hex (0x12345) address"); return; } diff --git a/src/hexEditorProvider.ts b/src/hexEditorProvider.ts index 38b3d218..2ebcd9e6 100644 --- a/src/hexEditorProvider.ts +++ b/src/hexEditorProvider.ts @@ -7,6 +7,7 @@ import * as vscode from "vscode"; import { HexDocumentEdit, HexDocumentEditOp, HexDocumentEditReference } from "../shared/hexDocumentModel"; import { Endianness, ExtensionHostMessageHandler, FromWebviewMessage, ICodeSettings, IEditorSettings, InspectorLocation, MessageHandler, MessageType, PasteMode, ToWebviewMessage } from "../shared/protocol"; import { deserializeEdits, serializeEdits } from "../shared/serialization"; +import { ILocalizedStrings, placeholder1 } from "../shared/strings"; import { DataInspectorView } from "./dataInspectorView"; import { disposeAll } from "./dispose"; import { HexDocument } from "./hexDocument"; @@ -59,6 +60,7 @@ export class HexEditorProvider implements vscode.CustomEditorProvider { if (document.isSynced) { // If we executed a save recently the change was probably caused by us @@ -70,20 +72,19 @@ export class HexEditorProvider implements vscode.CustomEditorProvider { - vscode.window.showWarningMessage("This file has been deleted! Saving now will create a new file on disk.", "Overwrite", "Close Editor").then((response) => { - if (response === "Overwrite") { + vscode.window.showWarningMessage(vscode.l10n.t("This file has been deleted! Saving now will create a new file on disk.", overwrite, vscode.l10n.t("Close Editor"))).then((response) => { + if (response === overwrite) { vscode.commands.executeCommand("workbench.action.files.save"); } else if (response === "Close Editor") { vscode.commands.executeCommand("workbench.action.closeActiveEditor"); @@ -154,6 +155,43 @@ export class HexEditorProvider implements vscode.CustomEditorProvider @@ -170,6 +208,7 @@ export class HexEditorProvider implements vscode.CustomEditorProvider + Hex Editor diff --git a/src/selectBetweenOffsets.ts b/src/selectBetweenOffsets.ts index 9232af47..fefabf29 100644 --- a/src/selectBetweenOffsets.ts +++ b/src/selectBetweenOffsets.ts @@ -7,83 +7,83 @@ const addressRe = /^0x[a-f0-9]+$/i; const decimalRe = /^[0-9]+$/i; export const showSelectBetweenOffsets = async (messaging: ExtensionHostMessageHandler, registry: HexEditorRegistry): Promise => { - messaging.sendEvent({ type: MessageType.StashDisplayedOffset }); + messaging.sendEvent({ type: MessageType.StashDisplayedOffset }); - let focusedOffset: string | undefined = undefined; + let focusedOffset: string | undefined = undefined; - // acquire selection state from active HexDocument - const selectionState: ISelectionState | undefined = registry.activeDocument?.selectionState; + // acquire selection state from active HexDocument + const selectionState: ISelectionState | undefined = registry.activeDocument?.selectionState; - // if there is a selection, use the focused offset as the starting offset - if (selectionState !== undefined && selectionState.selected > 0 && selectionState.focused !== undefined) { - // converting to hex to increase readability - focusedOffset = `0x${selectionState.focused.toString(16)}`; - } + // if there is a selection, use the focused offset as the starting offset + if (selectionState !== undefined && selectionState.selected > 0 && selectionState.focused !== undefined) { + // converting to hex to increase readability + focusedOffset = `0x${selectionState.focused.toString(16)}`; + } - const offset1 = await getOffset("Enter offset to select from", focusedOffset); - if (offset1 !== undefined) { - const offset2 = await getOffset("Enter offset to select until"); - if (offset2 !== undefined) { - messaging.sendEvent({ type: MessageType.SetFocusedByteRange, startingOffset: offset1, endingOffset: offset2 }); - } - } + const offset1 = await getOffset(vscode.l10n.t("Enter offset to select from"), focusedOffset); + if (offset1 !== undefined) { + const offset2 = await getOffset(vscode.l10n.t("Enter offset to select until")); + if (offset2 !== undefined) { + messaging.sendEvent({ type: MessageType.SetFocusedByteRange, startingOffset: offset1, endingOffset: offset2 }); + } + } - async function getOffset(inputBoxTitle: string, value?: string): Promise { - const disposables: vscode.Disposable[] = []; - try { - return await new Promise((resolve, _reject) => { - const input = vscode.window.createInputBox(); - input.title = inputBoxTitle; - input.value = value || ""; - input.prompt = inputBoxTitle; - input.ignoreFocusOut = true; - input.placeholder = inputBoxTitle; - disposables.push( - input.onDidAccept(() => { - const value = input.value; - input.enabled = false; - input.busy = true; - const offset = validate(value); - if (offset !== undefined) { - resolve(offset); - } - input.enabled = true; - input.busy = false; - }), - input.onDidChangeValue(text => { - const offset = validate(text); + async function getOffset(inputBoxTitle: string, value?: string): Promise { + const disposables: vscode.Disposable[] = []; + try { + return await new Promise((resolve, _reject) => { + const input = vscode.window.createInputBox(); + input.title = inputBoxTitle; + input.value = value || ""; + input.prompt = inputBoxTitle; + input.ignoreFocusOut = true; + input.placeholder = inputBoxTitle; + disposables.push( + input.onDidAccept(() => { + const value = input.value; + input.enabled = false; + input.busy = true; + const offset = validate(value); + if (offset !== undefined) { + resolve(offset); + } + input.enabled = true; + input.busy = false; + }), + input.onDidChangeValue(text => { + const offset = validate(text); - if (offset === undefined) { - input.validationMessage = "Offset must be provided as a decimal (12345) or hex (0x12345) address"; - } - else { - input.validationMessage = ""; - messaging.sendEvent({ type: MessageType.GoToOffset, offset: offset }); - } - }), - input.onDidHide(() => { - messaging.sendEvent({ type: MessageType.PopDisplayedOffset }); - resolve(undefined); - }), - input - ); - input.show(); - }); - } finally { - disposables.forEach(d => d.dispose()); - } + if (offset === undefined) { + input.validationMessage = "Offset must be provided as a decimal (12345) or hex (0x12345) address"; + } + else { + input.validationMessage = ""; + messaging.sendEvent({ type: MessageType.GoToOffset, offset: offset }); + } + }), + input.onDidHide(() => { + messaging.sendEvent({ type: MessageType.PopDisplayedOffset }); + resolve(undefined); + }), + input + ); + input.show(); + }); + } finally { + disposables.forEach(d => d.dispose()); + } - function validate(text: string): number | undefined { - let validatedOffset: number | undefined = undefined; - if (!text) { - validatedOffset = undefined; - } else if (addressRe.test(text)) { - validatedOffset = parseInt(text.slice(2), 16); - } else if (decimalRe.test(text)) { - validatedOffset = parseInt(text, 10); - } + function validate(text: string): number | undefined { + let validatedOffset: number | undefined = undefined; + if (!text) { + validatedOffset = undefined; + } else if (addressRe.test(text)) { + validatedOffset = parseInt(text.slice(2), 16); + } else if (decimalRe.test(text)) { + validatedOffset = parseInt(text, 10); + } - return validatedOffset; - } - } -}; \ No newline at end of file + return validatedOffset; + } + } +}; diff --git a/src/statusSelectionCount.ts b/src/statusSelectionCount.ts index 13c5a439..b66400ca 100644 --- a/src/statusSelectionCount.ts +++ b/src/statusSelectionCount.ts @@ -44,11 +44,11 @@ export default class StatusSelectionCount extends Disposable { const nFocus = focused !== undefined ? numberFormat.format(focused) : undefined; const nSelected = selected > 1 ? numberFormat.format(selected) : undefined; if (nFocus && nSelected) { - this.item.text = `Byte ${nFocus} (${nSelected} selected)`; + this.item.text = vscode.l10n.t("Byte {0} ({1} selected)", nFocus, nSelected); } else if (nSelected) { - this.item.text = `${nSelected} selected`; + this.item.text = vscode.l10n.t("{0} selected", nSelected); } else if (nFocus) { - this.item.text = `Byte ${nFocus}`; + this.item.text = vscode.l10n.t("Byte {0}", nFocus); } else { this.item.hide(); return; diff --git a/src/util.ts b/src/util.ts index ba01364e..4ea6420f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { window } from "vscode"; +import { l10n, window } from "vscode"; export function randomString(len = 32): string { let text = ""; @@ -19,7 +19,7 @@ export async function openOffsetInput(): Promise { return window.showInputBox({ placeHolder: "Enter offset to go to", validateInput: text => { - return text.length > 8 || new RegExp("^[a-fA-F0-9]+$").test(text) ? null : "Invalid offset string"; + return text.length > 8 || new RegExp("^[a-fA-F0-9]+$").test(text) ? null : l10n.t("Invalid offset string"); } }); } @@ -37,8 +37,8 @@ export const utf8Length = (str: string): number => { if (typeof Buffer !== "undefined") { return Buffer.byteLength(str); } else { - // todo: maybe doing some loops by hand here would be faster? does it matter? - return new Blob([str]).size; + // todo: maybe doing some loops by hand here would be faster? does it matter? + return new Blob([str]).size; } };