diff --git a/package-lock.json b/package-lock.json index 6366574d..35ed83c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22331,6 +22331,7 @@ "@jupyterlab/rendermime": "^4.0.0", "@jupyterlab/services": "^7.0.0", "@jupyterlab/settingregistry": "^4.0.0", + "@jupyterlab/translation": "^4.0.0", "@lumino/algorithm": "^2.0.1", "@lumino/coreutils": "^2.1.2", "@lumino/disposable": "^2.1.2", @@ -22343,6 +22344,7 @@ "@jupyterlab/docregistry": "^4.0.0", "@lumino/commands": "^2.0.0", "mkdirp": "^3.0.0", + "npm-run-all": "^4.1.5", "rimraf": "^5.0.0", "typescript": "^4.9.0" } diff --git a/package.json b/package.json index 050e405e..69a100eb 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "updated": "lerna updated", "watch:webapp": "run-p watch:lib watch:app", "watch:app": "lerna exec --stream --scope \"nbdime-webapp\" npm run watch", - "watch:lib": "lerna exec --stream --scope \"nbdime\" --scope \"nbdime-jupyterlab\" npm run watch" + "watch:lib": "lerna exec --stream --parallel --scope \"nbdime\" --scope \"nbdime-jupyterlab\" npm run watch" }, "devDependencies": { "@jupyterlab/buildutils": "^4.0.0", diff --git a/packages/labextension/package.json b/packages/labextension/package.json index ef90a8b6..318c1675 100644 --- a/packages/labextension/package.json +++ b/packages/labextension/package.json @@ -30,16 +30,19 @@ ], "style": "style/index.css", "scripts": { - "build": "npm run build:lib && npm run build:labextension", + "build": "npm run build:lib:prod && npm run build:labextension", "build:dev": "npm run build:lib && jupyter labextension build --development True .", "build:labextension": "rimraf ../../nbdime/labextension && mkdirp ../../nbdime/labextension && jupyter labextension build .", - "build:lib": "tsc --build", + "build:lib": "tsc --sourceMap", + "build:lib:prod": "tsc --build", "clean": "npm run clean:lib && npm run clean:labextension", "clean:labextension": "rimraf ../../nbdime/labextension", "clean:lib": "rimraf tsconfig.tsbuildinfo lib", "prepublishOnly": "npm run build", "update": "rimraf node_modules/nbdime && npm install && npm run build", - "watch": "tsc --build --watch" + "watch": "run-p watch:src watch:labextension", + "watch:src": "tsc -w --sourceMap", + "watch:labextension": "jupyter labextension watch ." }, "dependencies": { "@jupyterlab/apputils": "^4.0.0", @@ -50,6 +53,7 @@ "@jupyterlab/rendermime": "^4.0.0", "@jupyterlab/services": "^7.0.0", "@jupyterlab/settingregistry": "^4.0.0", + "@jupyterlab/translation": "^4.0.0", "@lumino/algorithm": "^2.0.1", "@lumino/coreutils": "^2.1.2", "@lumino/disposable": "^2.1.2", @@ -62,6 +66,7 @@ "@jupyterlab/docregistry": "^4.0.0", "@lumino/commands": "^2.0.0", "mkdirp": "^3.0.0", + "npm-run-all": "^4.1.5", "rimraf": "^5.0.0", "typescript": "^4.9.0" }, diff --git a/packages/labextension/src/actions.ts b/packages/labextension/src/actions.ts index 7d86819c..3143ef0a 100644 --- a/packages/labextension/src/actions.ts +++ b/packages/labextension/src/actions.ts @@ -1,11 +1,13 @@ +import type { CodeEditor } from '@jupyterlab/codeeditor'; + import { PathExt, URLExt } from '@jupyterlab/coreutils'; import type { IRenderMimeRegistry } from '@jupyterlab/rendermime'; -import type { CodeEditor } from '@jupyterlab/codeeditor'; - import { ServerConnection } from '@jupyterlab/services'; +import { nullTranslator, type ITranslator } from '@jupyterlab/translation'; + import type { Widget } from '@lumino/widgets'; import { NbdimeWidget } from './widget'; @@ -22,11 +24,13 @@ export function diffNotebook(args: { readonly rendermime: IRenderMimeRegistry; readonly editorFactory: CodeEditor.Factory; hideUnchanged?: boolean; + translator?: ITranslator; }): Widget { - let { base, remote } = args; + let { base, remote, translator } = args; + const trans = (translator ?? nullTranslator).load('nbdime'); let widget = new NbdimeWidget(args); - widget.title.label = `Diff: ${base} ↔ ${remote}`; - widget.title.caption = `Local: ${base}\nRemote: '${remote}'`; + widget.title.label = trans.__('Diff: %1 ↔ %2', base, remote); + widget.title.caption = trans.__("Local: '%1'\nRemote: '%2'", base, remote); return widget; } @@ -35,20 +39,27 @@ export function diffNotebookCheckpoint(args: { readonly rendermime: IRenderMimeRegistry; readonly editorFactory: CodeEditor.Factory; hideUnchanged?: boolean; + translator?: ITranslator; }): Widget { - const { path, rendermime, hideUnchanged, editorFactory } = args; + const { path, rendermime, hideUnchanged, editorFactory, translator } = args; + const trans = (translator ?? nullTranslator).load('nbdime'); let nb_dir = PathExt.dirname(path); let name = PathExt.basename(path, '.ipynb'); let base = PathExt.join(nb_dir, name + '.ipynb'); + let widget = new NbdimeWidget({ base, editorFactory, rendermime, - baseLabel: 'Checkpoint', + baseLabel: trans.__('Checkpoint'), hideUnchanged, + translator, }); - widget.title.label = `Diff checkpoint: ${name}`; - widget.title.caption = `Local: latest checkpoint\nRemote: '${path}'`; + widget.title.label = trans.__('Diff checkpoint: %1', name); + widget.title.caption = trans.__( + "Local: latest checkpoint\nRemote: '%1'", + path, + ); widget.title.iconClass = 'fa fa-clock-o jp-fa-tabIcon'; return widget; } @@ -58,17 +69,20 @@ export function diffNotebookGit(args: { readonly rendermime: IRenderMimeRegistry; readonly editorFactory: CodeEditor.Factory; hideUnchanged?: boolean; + translator?: ITranslator; }): Widget { - const { path, rendermime, hideUnchanged, editorFactory } = args; + const { path, rendermime, hideUnchanged, editorFactory, translator } = args; + const trans = (translator ?? nullTranslator).load('nbdime'); let name = PathExt.basename(path, '.ipynb'); let widget = new NbdimeWidget({ base: path, editorFactory, rendermime, hideUnchanged, + translator, }); - widget.title.label = `Diff git: ${name}`; - widget.title.caption = `Local: git HEAD\nRemote: '${path}'`; + widget.title.label = trans.__('Diff git: %1', name); + widget.title.caption = trans.__("Local: git HEAD\nRemote: '%1'", path); widget.title.iconClass = 'fa fa-git jp-fa-tabIcon'; return widget; } diff --git a/packages/labextension/src/plugin.ts b/packages/labextension/src/plugin.ts index f6c8e290..1e089419 100644 --- a/packages/labextension/src/plugin.ts +++ b/packages/labextension/src/plugin.ts @@ -22,6 +22,8 @@ import { NotebookPanel, INotebookTracker } from '@jupyterlab/notebook'; import { ISettingRegistry } from '@jupyterlab/settingregistry'; +import { ITranslator, nullTranslator } from '@jupyterlab/translation'; + import { find } from '@lumino/algorithm'; import type { CommandRegistry } from '@lumino/commands'; @@ -32,12 +34,6 @@ import { diffNotebookGit, diffNotebookCheckpoint, isNbInGit } from './actions'; const pluginId = 'nbdime-jupyterlab:plugin'; -/** - * Error message if the nbdime API is unavailable. - */ -const serverMissingMsg = - 'Unable to query nbdime API. Is the server extension enabled?'; - const INITIAL_NETWORK_RETRY = 2; // ms export class NBDiffExtension @@ -116,11 +112,20 @@ function addCommands( rendermime: IRenderMimeRegistry, settings: ISettingRegistry.ISettings, editorServices: IEditorServices, + translator: ITranslator, ): void { const { commands, shell } = app; const editorFactory = editorServices.factoryService.newInlineEditor.bind( editorServices.factoryService, ); + const trans = translator.load('nbdime'); + + /** + * Error message if the nbdime API is unavailable. + */ + const serverMissingMsg = trans.__( + 'Unable to query nbdime API. Is the server extension enabled?', + ); // Whether we have our server extension available let hasAPI = true; @@ -194,10 +199,10 @@ function addCommands( // TODO: Check args for base/remote // if missing, prompt with dialog. //let content = current.notebook; - //diffNotebook({base, remote}); + //diffNotebook({base, remote, translator}); }, - label: erroredGen('Notebook diff'), - caption: erroredGen('Display nbdiff between two notebooks'), + label: erroredGen(trans.__('Notebook diff')), + caption: erroredGen(trans.__('Display nbdiff between two notebooks')), isEnabled: baseEnabled, iconClass: 'jp-Icon jp-Icon-16 action-notebook-diff action-notebook-diff-notebooks', @@ -215,15 +220,16 @@ function addCommands( editorFactory, rendermime, hideUnchanged, + translator, }); shell.add(widget); if (args['activate'] !== false) { shell.activateById(widget.id); } }, - label: erroredGen('Notebook checkpoint diff'), + label: erroredGen(trans.__('Notebook checkpoint diff')), caption: erroredGen( - 'Display nbdiff from checkpoint to currently saved version', + trans.__('Display nbdiff from checkpoint to currently saved version'), ), isEnabled: baseEnabled, iconClass: @@ -241,15 +247,16 @@ function addCommands( editorFactory, rendermime, hideUnchanged, + translator, }); shell.add(widget); if (args['activate'] !== false) { shell.activateById(widget.id); } }, - label: erroredGen('Notebook Git diff'), + label: erroredGen(trans.__('Notebook Git diff')), caption: erroredGen( - 'Display nbdiff from git HEAD to currently saved version', + trans.__('Display nbdiff from git HEAD to currently saved version'), ), isEnabled: hasGitNotebook, iconClass: @@ -268,6 +275,7 @@ const nbDiffProvider: JupyterFrontEndPlugin = { ISettingRegistry, IEditorServices, ], + optional: [ITranslator], activate: activateWidgetExtension, autoStart: true, }; @@ -283,13 +291,21 @@ async function activateWidgetExtension( rendermime: IRenderMimeRegistry, settingsRegistry: ISettingRegistry, editorServices: IEditorServices, + translator: ITranslator | null, ): Promise { let { commands, docRegistry } = app; let extension = new NBDiffExtension(commands); docRegistry.addWidgetExtension('Notebook', extension); const settings = await settingsRegistry.load(pluginId); - addCommands(app, tracker, rendermime, settings, editorServices); + addCommands( + app, + tracker, + rendermime, + settings, + editorServices, + translator ?? nullTranslator, + ); // Update the command registry when the notebook state changes. tracker.currentChanged.connect(() => { commands.notifyCommandChanged(CommandIDs.diffNotebookGit); diff --git a/packages/labextension/src/widget.ts b/packages/labextension/src/widget.ts index f4de2035..686c2adb 100644 --- a/packages/labextension/src/widget.ts +++ b/packages/labextension/src/widget.ts @@ -6,6 +6,12 @@ import type { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { ServerConnection } from '@jupyterlab/services'; +import { + nullTranslator, + type ITranslator, + type TranslationBundle, +} from '@jupyterlab/translation'; + import type { JSONObject } from '@lumino/coreutils'; import type { Message } from '@lumino/messaging'; @@ -53,6 +59,8 @@ export class NbdimeWidget extends Panel { this.remote = options.remote; this.editorFactory = options.editorFactory; this.rendermime = options.rendermime; + this.translator = options.translator ?? nullTranslator; + this.trans = this.translator.load('nbdime'); let header = Private.diffHeader(options); this.addWidget(header); @@ -116,10 +124,17 @@ export class NbdimeWidget extends Panel { let base = data['base'] as nbformat.INotebookContent; let diff = data['diff'] as any as IDiffEntry[]; let nbdModel = new NotebookDiffModel(base, diff); + if (nbdModel.metadata) { + const trans = this.translator.load('nbdime'); + nbdModel.metadata.collapsibleHeader = trans.__( + 'Notebook metadata changed', + ); + } let nbdWidget = new NotebookDiffWidget({ model: nbdModel, rendermime: this.rendermime, editorFactory: this.editorFactory, + translator: this.translator, }); this.scroller.addWidget(nbdWidget); @@ -135,7 +150,7 @@ export class NbdimeWidget extends Panel { return; } let widget = new Widget(); - widget.node.innerHTML = `Failed to fetch diff: ${error}`; + widget.node.textContent = this.trans.__('Failed to fetch diff: %1', error); this.scroller.addWidget(widget); } @@ -144,6 +159,8 @@ export class NbdimeWidget extends Panel { protected editorFactory: CodeEditor.Factory; protected rendermime: IRenderMimeRegistry; + protected translator: ITranslator; + protected trans: TranslationBundle; protected header: Widget; protected scroller: Panel; @@ -193,6 +210,11 @@ export namespace NbdimeWidget { * Whether to hide unchanged cells by default. */ hideUnchanged?: boolean; + + /** + * Application translator. + */ + translator?: ITranslator; } } @@ -201,7 +223,8 @@ namespace Private { * Create a header widget for the diff view. */ export function diffHeader(options: NbdimeWidget.IOptions): Widget { - let { base, remote, baseLabel, remoteLabel } = options; + let { base, remote, baseLabel, remoteLabel, translator } = options; + const trans = (translator ?? nullTranslator).load('nbdime'); if (remote) { if (baseLabel === undefined) { baseLabel = base; @@ -220,19 +243,24 @@ namespace Private { node.className = 'nbdime-Diff'; node.innerHTML = `
- - + +
`; + node + .querySelector('.nbdime-header-buttonrow > label')! + .insertAdjacentText('beforeend', trans.__('Hide unchanged cells')); + node.querySelector('button.nbdime-export')!.textContent = + trans.__('Export diff'); ( node.getElementsByClassName('nbdime-header-base')[0] as HTMLSpanElement - ).innerText = baseLabel; + ).textContent = baseLabel; ( node.getElementsByClassName('nbdime-header-remote')[0] as HTMLSpanElement - ).innerText = remoteLabel; + ).textContent = remoteLabel; return new Widget({ node }); } diff --git a/packages/nbdime/src/common/basepanel.ts b/packages/nbdime/src/common/basepanel.ts index 0fe6a996..abf9bf99 100644 --- a/packages/nbdime/src/common/basepanel.ts +++ b/packages/nbdime/src/common/basepanel.ts @@ -1,13 +1,17 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. +import type { CodeEditor } from '@jupyterlab/codeeditor'; + +import { type ITranslator, nullTranslator } from '@jupyterlab/translation'; + import { Panel } from '@lumino/widgets'; + import type { IDiffViewOptions, IDiffWidgetOptions, IMergeViewOptions, } from './interfaces'; -import type { CodeEditor } from '@jupyterlab/codeeditor'; /** * Common panel for diff views @@ -19,16 +23,19 @@ export class DiffPanel< constructor({ model, editorFactory, + translator, ...viewOptions }: IDiffWidgetOptions & U) { super(); this._editorFactory = editorFactory; this._model = model; + this._translator = translator ?? nullTranslator; this._viewOptions = viewOptions as U; } protected _editorFactory: CodeEditor.Factory | undefined; protected _model: T; + protected _translator: ITranslator; protected _viewOptions: U; } diff --git a/packages/nbdime/src/common/collapsiblepanel.ts b/packages/nbdime/src/common/collapsiblepanel.ts index 66ca5869..d642aeba 100644 --- a/packages/nbdime/src/common/collapsiblepanel.ts +++ b/packages/nbdime/src/common/collapsiblepanel.ts @@ -22,9 +22,7 @@ export class CollapsiblePanel extends Panel { let header = new Panel(); header.addClass(COLLAPSIBLE_HEADER); if (headerTitle) { - // let title = document.createElement('span'); - header.node.innerText = headerTitle; - // header.appendChild(title); + header.node.textContent = headerTitle; } let button = document.createElement('button'); button.className = COLLAPSIBLE_HEADER_ICON; @@ -37,8 +35,7 @@ export class CollapsiblePanel extends Panel { super(); this.addClass(COLLAPSIBLE_CLASS); this.inner = inner; - let constructor = this.constructor as typeof CollapsiblePanel; - let header = constructor.createHeader(headerTitle); + let header = CollapsiblePanel.createHeader(headerTitle); this.header = header; this.button = header.node.getElementsByClassName( COLLAPSIBLE_HEADER_ICON, diff --git a/packages/nbdime/src/common/interfaces.ts b/packages/nbdime/src/common/interfaces.ts index f1d76cb3..24adfffd 100644 --- a/packages/nbdime/src/common/interfaces.ts +++ b/packages/nbdime/src/common/interfaces.ts @@ -1,5 +1,6 @@ import type { CodeEditor } from '@jupyterlab/codeeditor'; import type { IRenderMimeRegistry } from '@jupyterlab/rendermime'; +import type { ITranslator } from '@jupyterlab/translation'; /** * Diff view options @@ -11,6 +12,10 @@ export interface IDiffViewOptions { * around such stretches (which defaults to 2). Defaults to true. */ collapseIdentical?: boolean | number; + /** + * The translation manager. + */ + translator?: ITranslator; } /** diff --git a/packages/nbdime/src/common/mergeview.ts b/packages/nbdime/src/common/mergeview.ts index a9bb3cb2..26613dfd 100644 --- a/packages/nbdime/src/common/mergeview.ts +++ b/packages/nbdime/src/common/mergeview.ts @@ -14,6 +14,7 @@ import { ChangeDesc, RangeSetBuilder, RangeSet, + EditorState, } from '@codemirror/state'; import { @@ -28,6 +29,12 @@ import { import type { CodeEditor } from '@jupyterlab/codeeditor'; +import { + type ITranslator, + nullTranslator, + type TranslationBundle, +} from '@jupyterlab/translation'; + import { Widget, Panel } from '@lumino/widgets'; import type { IStringDiffModel } from '../diff/model'; @@ -549,6 +556,7 @@ export function createNbdimeMergeView( factory, collapseIdentical, showBase, + translator, } = options; let opts: IMergeViewEditorConfiguration = { remote, @@ -558,6 +566,7 @@ export function createNbdimeMergeView( factory: factory ?? createEditorFactory(), collapseIdentical, showBase, + translator, }; let mergeview = new MergeView(opts); @@ -603,14 +612,25 @@ export interface IDiffViewOptions { * Whether to synchronize scrolling between the two editors or not. */ lockScroll?: boolean; + /** + * The configuration options for the editor. + */ + translator?: ITranslator; } /** * Used by MergeView to show diff in a string diff model */ export class DiffView { - constructor({ model, type, options, lockScroll }: IDiffViewOptions) { + constructor({ + model, + type, + options, + lockScroll, + translator, + }: IDiffViewOptions) { this._model = model; + this._trans = (translator ?? nullTranslator).load('nbdime'); this._type = type; this._lockScroll = lockScroll ?? true; let remoteValue = this._model.remote || ''; @@ -705,7 +725,7 @@ export class DiffView { undefined, 'cm-merge-scrolllock', )); - lock.title = 'Toggle locked scrolling'; + lock.title = this._trans.__('Toggle locked scrolling'); this.setScrollLock(this._lockScroll); lock.addEventListener('click', event => { this.setScrollLock(!this._lockScroll); @@ -1145,6 +1165,7 @@ export class DiffView { private _baseEditorWidget: EditorWidget; private _remoteEditorWidget: EditorWidget; private _model: IStringDiffModel; + private _trans: TranslationBundle; private _type: string; private _chunks: Chunk[]; private _lineChunks: Chunk[]; @@ -1332,6 +1353,7 @@ export class MergeView extends Panel { constructor(options: IMergeViewEditorConfiguration) { super(); this._measuring = -1; + this._trans = (options.translator ?? nullTranslator).load('nbdime'); let remote = options.remote; let local = options.local || null; let merged = options.merged || null; @@ -1444,7 +1466,13 @@ export class MergeView extends Panel { const additionalExtensions = inMergeView ? [listener, mergeControlGutter, getCommonEditorExtensions(inMergeView)] : getCommonEditorExtensions(inMergeView); - const singlePane = !merged && (remote?.unchanged || remote?.added || remote?.deleted) + additionalExtensions.push( + EditorState.phrases.of({ + '(…$ unchanged lines…)': this._trans.__('(…$ unchanged lines…)'), + }), + ); + const singlePane = + !merged && (remote?.unchanged || remote?.added || remote?.deleted); if ( // no collapse this._collapseIdentical >= 0 && @@ -1470,7 +1498,7 @@ export class MergeView extends Panel { // Local value was deleted left = this._left = null; leftWidget = new Widget({ - node: elt('div', 'Value missing', 'jp-mod-missing'), + node: elt('div', this._trans.__('Value missing'), 'jp-mod-missing'), }); } else { left = this._left = new DiffView({ @@ -1483,6 +1511,7 @@ export class MergeView extends Panel { extensions: [options.extensions ?? [], additionalExtensions], }, lockScroll: showBase, + translator: options.translator, }); leftWidget = left.remoteEditorWidget; } @@ -1501,7 +1530,7 @@ export class MergeView extends Panel { // Remote value was deleted right = this._right = null; rightWidget = new Widget({ - node: elt('div', 'Value missing', 'jp-mod-missing'), + node: elt('div', this._trans.__('Value missing'), 'jp-mod-missing'), }); } else { right = this._right = new DiffView({ @@ -1514,6 +1543,7 @@ export class MergeView extends Panel { extensions: [options.extensions ?? [], additionalExtensions], }, lockScroll: showBase, + translator: options.translator, }); rightWidget = right.remoteEditorWidget; } @@ -1531,6 +1561,7 @@ export class MergeView extends Panel { extensions: [options.extensions ?? [], additionalExtensions], }, lockScroll: showBase, + translator: options.translator, }); let mergeWidget = merge.remoteEditorWidget; if (showBase) { @@ -1608,6 +1639,7 @@ export class MergeView extends Panel { config: { ...options.config }, extensions: [options.extensions ?? [], additionalExtensions], }, + translator: options.translator, }); let rightWidget = right.remoteEditorWidget; rightWidget.addClass('cm-merge-pane'); @@ -1977,6 +2009,7 @@ export class MergeView extends Panel { ) as DiffView[]; } + private _trans: TranslationBundle; private _collapseIdentical: number = 2; private _left: DiffView | null; private _right: DiffView | null; diff --git a/packages/nbdime/src/diff/widget/cell.ts b/packages/nbdime/src/diff/widget/cell.ts index 3cfef585..c5ae79fd 100644 --- a/packages/nbdime/src/diff/widget/cell.ts +++ b/packages/nbdime/src/diff/widget/cell.ts @@ -4,15 +4,18 @@ import { Panel, Widget } from '@lumino/widgets'; -import type { CodeEditor } from '@jupyterlab/codeeditor'; - import { IRenderMimeRegistry, MimeModel } from '@jupyterlab/rendermime'; +import type { TranslationBundle } from '@jupyterlab/translation'; + import { DiffPanel } from '../../common/basepanel'; import { CollapsiblePanel } from '../../common/collapsiblepanel'; -import type { ICellDiffWidgetOptions } from '../../common/interfaces'; +import type { + ICellDiffWidgetOptions, + IMimeDiffWidgetOptions, +} from '../../common/interfaces'; import { createNbdimeMergeView } from '../../common/mergeview'; @@ -53,12 +56,10 @@ const SOURCE_ROW_CLASS = 'jp-Cellrow-source'; const METADATA_ROW_CLASS = 'jp-Cellrow-metadata'; const OUTPUTS_ROW_CLASS = 'jp-Cellrow-outputs'; -export interface ICellDiffViewOptions { - model: T; +export interface ICellDiffViewOptions + extends IMimeDiffWidgetOptions { parent: CellDiffModel; editorClasses: string[]; - rendermime: IRenderMimeRegistry; - editorFactory?: CodeEditor.Factory; } /** @@ -76,6 +77,7 @@ export class CellDiffWidget extends DiffPanel { super(others); this.addClass(CELLDIFF_CLASS); this._rendermime = rendermime; + this._trans = this._translator.load('nbdime'); this.mimetype = mimetype; this.init(); @@ -105,6 +107,7 @@ export class CellDiffWidget extends DiffPanel { editorClasses: CURR_DIFF_CLASSES, rendermime: this._rendermime, editorFactory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }); sourceView.addClass(SOURCE_ROW_CLASS); @@ -123,6 +126,7 @@ export class CellDiffWidget extends DiffPanel { editorClasses: CURR_DIFF_CLASSES, rendermime: this._rendermime, editorFactory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }); metadataView.addClass(METADATA_ROW_CLASS); @@ -142,6 +146,7 @@ export class CellDiffWidget extends DiffPanel { editorClasses: CURR_DIFF_CLASSES, rendermime: this._rendermime, editorFactory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }); container.addWidget(outputsWidget); @@ -162,6 +167,7 @@ export class CellDiffWidget extends DiffPanel { editorClasses: CURR_DIFF_CLASSES, rendermime: this._rendermime, editorFactory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }); target.addWidget(outputsWidget); @@ -177,7 +183,9 @@ export class CellDiffWidget extends DiffPanel { this.addWidget(container); } else { let collapsed = !changed; - let header = changed ? 'Outputs changed' : 'Outputs unchanged'; + let header = changed + ? this._trans.__('Outputs changed') + : this._trans.__('Outputs unchanged'); let collapser = new CollapsiblePanel(container, header, collapsed); collapser.addClass(OUTPUTS_ROW_CLASS); this.addWidget(collapser); @@ -221,6 +229,7 @@ export class CellDiffWidget extends DiffPanel { editorClasses, rendermime, editorFactory, + translator, ...viewOptions }: ICellDiffViewOptions): Panel { let view: Panel; @@ -241,6 +250,7 @@ export class CellDiffWidget extends DiffPanel { inner = createNbdimeMergeView({ remote: model, factory: editorFactory, + translator, ...viewOptions, }); } @@ -261,6 +271,7 @@ export class CellDiffWidget extends DiffPanel { editorClasses, rendermime, editorFactory, + translator, }); if (model.added) { view.addClass(ADDED_DIFF_CLASS); @@ -290,4 +301,5 @@ export class CellDiffWidget extends DiffPanel { } protected _rendermime: IRenderMimeRegistry; + protected _trans: TranslationBundle; } diff --git a/packages/nbdime/src/diff/widget/metadata.ts b/packages/nbdime/src/diff/widget/metadata.ts index cfacf5fe..5e827ad7 100644 --- a/packages/nbdime/src/diff/widget/metadata.ts +++ b/packages/nbdime/src/diff/widget/metadata.ts @@ -36,6 +36,7 @@ export class MetadataDiffWidget extends DiffPanel { let view: Widget = createNbdimeMergeView({ remote: model, factory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }); if (model.collapsible) { diff --git a/packages/nbdime/src/diff/widget/notebook.ts b/packages/nbdime/src/diff/widget/notebook.ts index 42098351..cc72dedf 100644 --- a/packages/nbdime/src/diff/widget/notebook.ts +++ b/packages/nbdime/src/diff/widget/notebook.ts @@ -53,6 +53,7 @@ export class NotebookDiffWidget extends DiffPanel { new MetadataDiffWidget({ model: model.metadata, editorFactory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }), ); @@ -68,6 +69,7 @@ export class NotebookDiffWidget extends DiffPanel { rendermime, mimetype: model.mimetype, editorFactory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }), ); @@ -86,6 +88,7 @@ export class NotebookDiffWidget extends DiffPanel { rendermime, mimetype: model.mimetype, editorFactory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }), ); diff --git a/packages/nbdime/src/diff/widget/output.ts b/packages/nbdime/src/diff/widget/output.ts index aed8fb90..dc30bb2e 100644 --- a/packages/nbdime/src/diff/widget/output.ts +++ b/packages/nbdime/src/diff/widget/output.ts @@ -4,6 +4,8 @@ import type * as nbformat from '@jupyterlab/nbformat'; +import type { TranslationBundle } from '@jupyterlab/translation'; + import { Panel, Widget } from '@lumino/widgets'; import { each, find, toArray } from '@lumino/algorithm'; @@ -107,6 +109,7 @@ export class OutputPanel extends DiffPanel { }: ICellDiffViewOptions) { super(others); this.rendermime = rendermime; + this._trans = this._translator.load('nbdime'); this.editorClasses = editorClasses; this._model.trustedChanged.connect( @@ -122,7 +125,7 @@ export class OutputPanel extends DiffPanel { if (!parentModel.added) { // Implies this is added output let addSpacer = new Widget(); - addSpacer.node.textContent = 'Output added'; + addSpacer.node.textContent = this._trans.__('Output added'); addSpacer.addClass(ADD_DEL_LABEL_CLASS); this.addWidget(addSpacer); } @@ -131,7 +134,7 @@ export class OutputPanel extends DiffPanel { if (!parentModel.deleted) { // Implies this is deleted output let delSpacer = new Widget(); - delSpacer.node.textContent = 'Output deleted'; + delSpacer.node.textContent = this._trans.__('Output deleted'); delSpacer.addClass(ADD_DEL_LABEL_CLASS); this.addWidget(delSpacer); } @@ -233,6 +236,7 @@ export class OutputPanel extends DiffPanel { view = createNbdimeMergeView({ remote: stringModel, factory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }); } @@ -242,6 +246,7 @@ export class OutputPanel extends DiffPanel { view = createNbdimeMergeView({ remote: model.stringify(), factory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }); } @@ -260,11 +265,11 @@ export class OutputPanel extends DiffPanel { // Add rendered/source toggle: let btnSource = document.createElement('button'); - let sourceText = ['Show source', 'Render']; - btnSource.innerText = sourceText[0]; + let sourceText = [this._trans.__('Show source'), this._trans.__('Render')]; + btnSource.textContent = sourceText[0]; btnSource.onclick = (ev: MouseEvent) => { this.forceText = !this.forceText; - btnSource.innerText = sourceText[this.forceText ? 1 : 0]; + btnSource.textContent = sourceText[this.forceText ? 1 : 0]; this.updateView(); }; let w = new Widget({ node: btnSource }); @@ -273,7 +278,7 @@ export class OutputPanel extends DiffPanel { // Add trust button: let btnTrust = document.createElement('button'); - btnTrust.innerText = 'Trust'; + btnTrust.textContent = this._trans.__('Trust'); btnTrust.onclick = (ev: MouseEvent) => { // Triggers change event: this._model.trusted = !this._model.trusted; @@ -375,6 +380,7 @@ export class OutputPanel extends DiffPanel { protected menu: Panel; protected _mimetype: string | null = null; + protected _trans: TranslationBundle; protected forceText = false; /** diff --git a/packages/nbdime/src/merge/widget/cell.ts b/packages/nbdime/src/merge/widget/cell.ts index 5eeb4f42..027483bb 100644 --- a/packages/nbdime/src/merge/widget/cell.ts +++ b/packages/nbdime/src/merge/widget/cell.ts @@ -10,6 +10,8 @@ import type { CodeEditor } from '@jupyterlab/codeeditor'; import type { IRenderMimeRegistry } from '@jupyterlab/rendermime'; +import type { TranslationBundle } from '@jupyterlab/translation'; + import { MergePanel } from '../../common/basepanel'; import { CollapsiblePanel } from '../../common/collapsiblepanel'; @@ -81,18 +83,14 @@ export interface ICellMergeViewOptions { */ export class CellMergeWidget extends MergePanel { static createMergeView({ - editorFactory, - local, - remote, merged, + editorFactory, readOnly, ...others }: ICellMergeViewOptions & IMergeViewOptions): Widget | null { let view: Widget | null = null; if (merged instanceof StringDiffModel) { view = createNbdimeMergeView({ - remote, - local, merged, readOnly: readOnly ?? false, factory: editorFactory, @@ -132,6 +130,7 @@ export class CellMergeWidget extends MergePanel { super(options); this.addClass(CELLMERGE_CLASS); this._rendermime = rendermime; + this._trans = this._translator.load('nbdime'); this.mimetype = mimetype; this.init(); @@ -191,9 +190,13 @@ export class CellMergeWidget extends MergePanel { let radd = model.remote && model.remote.added; let rdel = model.remote && model.remote.deleted; if ((ladd && !radd) || (ldel && !rdel)) { - this.headerTitle = ladd ? 'Cell added locally' : 'Cell deleted locally'; + this.headerTitle = ladd + ? this._trans.__('Cell added locally') + : this._trans.__('Cell deleted locally'); } else if ((radd && !ladd) || (rdel && !ldel)) { - this.headerTitle = radd ? 'Cell added remotely' : 'Cell deleted remotely'; + this.headerTitle = radd + ? this._trans.__('Cell added remotely') + : this._trans.__('Cell deleted remotely'); } if ( @@ -216,16 +219,17 @@ export class CellMergeWidget extends MergePanel { editorClasses: CURR_CLASSES, rendermime: this._rendermime, editorFactory: this._editorFactory, + translator: this._translator, }); if ((ladd && !radd) || (ldel && !rdel)) { this.addClass(ONEWAY_LOCAL_CLASS); } else if ((radd && !ladd) || (rdel && !ldel)) { this.addClass(ONEWAY_REMOTE_CLASS); } else if (ldel && rdel) { - this.headerTitle = 'Deleted on both sides'; + this.headerTitle = this._trans.__('Deleted on both sides'); this.addClass(TWOWAY_DELETION_CLASS); } else if (ladd && radd) { - this.headerTitle = 'Added on both sides'; + this.headerTitle = this._trans.__('Added on both sides'); this.addClass(TWOWAY_ADDITION_CLASS); } view.addClass(SOURCE_ROW_CLASS); @@ -241,6 +245,7 @@ export class CellMergeWidget extends MergePanel { editorClasses: CURR_CLASSES, rendermime: this._rendermime, editorFactory: this._editorFactory, + translator: this._translator, }); container.addWidget(view); } @@ -255,7 +260,9 @@ export class CellMergeWidget extends MergePanel { let row = new FlexPanel({ direction: 'left-to-right' }); row.addClass(EXECUTIONCOUNT_ROW_CLASS); let textWidget = new Widget(); - textWidget.node.innerText = 'Execution count will be cleared.'; + textWidget.node.textContent = this._trans.__( + 'Execution count will be cleared.', + ); row.addWidget(textWidget); this.addWidget(row); } @@ -274,6 +281,7 @@ export class CellMergeWidget extends MergePanel { editorClasses: CURR_CLASSES, rendermime: this._rendermime, editorFactory: this._editorFactory, + translator: this._translator, }); } else { sourceView = CellMergeWidget.createMergeView({ @@ -282,6 +290,7 @@ export class CellMergeWidget extends MergePanel { merged: model.merged.source, editorClasses: CURR_CLASSES, editorFactory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }); } @@ -316,6 +325,7 @@ export class CellMergeWidget extends MergePanel { merged: model.merged.metadata, editorClasses: CURR_CLASSES, editorFactory: this._editorFactory, + translator: this._translator, ...this._viewOptions, readOnly: true, // Do not allow manual edit of metadata }); @@ -328,7 +338,7 @@ export class CellMergeWidget extends MergePanel { let container = new Panel(); container.addWidget(metadataView); - let header = 'Metadata changed'; + let header = this._trans.__('Metadata changed'); let collapser = new CollapsiblePanel(container, header, true); collapser.addClass(METADATA_ROW_CLASS); this.addWidget(collapser); @@ -360,9 +370,9 @@ export class CellMergeWidget extends MergePanel { let header = outputsChanged ? model.outputsConflicted - ? 'Outputs conflicted' - : 'Outputs changed' - : 'Outputs unchanged'; + ? this._trans.__('Outputs conflicted') + : this._trans.__('Outputs changed') + : this._trans.__('Outputs unchanged'); let collapser = new CollapsiblePanel(view, header, !outputsChanged); collapser.addClass(OUTPUTS_ROW_CLASS); @@ -378,12 +388,12 @@ export class CellMergeWidget extends MergePanel { } model.clearOutputConflicts(); collapser.removeClass(OUTPUTS_CONFLICTED_CLASS); - collapser.headerTitle = 'Outputs changed'; + collapser.headerTitle = this._trans.__('Outputs changed'); ev.preventDefault(); ev.stopPropagation(); conflictClearBtn.parent = null!; }; - btn.innerText = 'Mark resolved'; + btn.textContent = this._trans.__('Mark resolved'); node.appendChild(btn); collapser.header.insertWidget(1, conflictClearBtn); } @@ -425,7 +435,7 @@ export class CellMergeWidget extends MergePanel { private _createClearOutputToggle(): Widget { let { checkbox, widget } = createCheckbox( this.model.clearOutputs, - 'Clear outputs', + this._trans.__('Clear outputs'), ); if (this.model.clearOutputs) { this.addClass(MARKED_CLEAR_OUTPUTS); @@ -447,7 +457,7 @@ export class CellMergeWidget extends MergePanel { private _createDeleteToggle(): Widget { let { checkbox, widget } = createCheckbox( this.model.deleteCell, - 'Delete cell', + this._trans.__('Delete cell'), ); if (this.model.deleteCell) { this.addClass(MARKED_DELETE); @@ -490,4 +500,5 @@ export class CellMergeWidget extends MergePanel { } private _rendermime: IRenderMimeRegistry; + protected _trans: TranslationBundle; } diff --git a/packages/nbdime/src/merge/widget/common.ts b/packages/nbdime/src/merge/widget/common.ts index 5f2aacd7..745c03d5 100644 --- a/packages/nbdime/src/merge/widget/common.ts +++ b/packages/nbdime/src/merge/widget/common.ts @@ -43,7 +43,7 @@ export function createCheckbox( // Create label for checkbox: let widget = new Widget(); let label = document.createElement('label'); - label.innerHTML = text; + label.textContent = text; // Combine checkbox and label: label.insertBefore(checkbox, label.childNodes[0]); // Add checkbox to header: diff --git a/packages/nbdime/src/merge/widget/dragdrop.ts b/packages/nbdime/src/merge/widget/dragdrop.ts index cb0e7634..4ba1e650 100644 --- a/packages/nbdime/src/merge/widget/dragdrop.ts +++ b/packages/nbdime/src/merge/widget/dragdrop.ts @@ -2,6 +2,8 @@ // Distributed under the terms of the Modified BSD License. 'use strict'; +import { nullTranslator, type ITranslator } from '@jupyterlab/translation'; + import { Panel, Widget } from '@lumino/widgets'; import { Signal, ISignal } from '@lumino/signaling'; @@ -14,7 +16,7 @@ const CELL_DRAG_DROP_CLASS = 'jp-merge-celldragdrop'; const MARK_CHUNK_RESOLVED_CLASS = 'jp-conflicted-cells-button'; const CHUNK_HEADER_CLASS = 'jp-conflicted-cells-header'; -const CONLICTED_CELL_CHUNK_CLASS = 'jp-conflicted-cells'; +const CONFLICTED_CELL_CHUNK_CLASS = 'jp-conflicted-cells'; export class CellsDragDrop extends FriendlyDragDrop { /** @@ -84,14 +86,15 @@ export class ChunkedCellsWidget extends Panel { /** * */ - constructor() { + constructor({ translator }: { translator?: ITranslator } = {}) { super(); - this.addClass(CONLICTED_CELL_CHUNK_CLASS); + const trans = (translator ?? nullTranslator).load('nbdime'); + this.addClass(CONFLICTED_CELL_CHUNK_CLASS); this.header = new Widget(); this.header.addClass(CHUNK_HEADER_CLASS); - this.header.node.innerText = 'Conflicting cell operations'; + this.header.node.textContent = trans.__('Conflicting cell operations'); let button = document.createElement('button'); - button.innerText = 'Resolve Conflict'; + button.textContent = trans.__('Resolve Conflict'); button.onclick = this.onResolve.bind(this); button.className = MARK_CHUNK_RESOLVED_CLASS; this.header.node.appendChild(button); @@ -109,7 +112,7 @@ export class ChunkedCellsWidget extends Panel { } } } - this.removeClass(CONLICTED_CELL_CHUNK_CLASS); + this.removeClass(CONFLICTED_CELL_CHUNK_CLASS); this.header.parent = null; this.header.dispose(); this._resolved.emit(undefined); diff --git a/packages/nbdime/src/merge/widget/metadata.ts b/packages/nbdime/src/merge/widget/metadata.ts index 6503c8d6..3fe19f85 100644 --- a/packages/nbdime/src/merge/widget/metadata.ts +++ b/packages/nbdime/src/merge/widget/metadata.ts @@ -41,11 +41,13 @@ export class MetadataMergeWidget extends MergePanel { local: model.local, merged: model.merged, factory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }); + const trans = this._translator.load('nbdime'); const wrapper = new CollapsiblePanel( this.view, - 'Notebook metadata changed', + trans.__('Notebook metadata changed'), true, ); this.addWidget(wrapper); diff --git a/packages/nbdime/src/merge/widget/notebook.ts b/packages/nbdime/src/merge/widget/notebook.ts index 586c7cbe..fd8ce2f9 100644 --- a/packages/nbdime/src/merge/widget/notebook.ts +++ b/packages/nbdime/src/merge/widget/notebook.ts @@ -6,6 +6,8 @@ import type * as nbformat from '@jupyterlab/nbformat'; import type { IRenderMimeRegistry } from '@jupyterlab/rendermime'; +import type { ITranslator, TranslationBundle } from '@jupyterlab/translation'; + import { MergePanel } from '../../common/basepanel'; import type { @@ -54,12 +56,15 @@ export class NotebookMergeWidget extends MergePanel { let rendermime = this._rendermime; let work = Promise.resolve(); - this.addWidget(new NotebookMergeControls(model)); + this.addWidget( + new NotebookMergeControls({ model, translator: this._translator }), + ); work = work.then(() => { if (model.metadata) { this.metadataWidget = new MetadataMergeWidget({ model: model.metadata, editorFactory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }); this.addWidget(this.metadataWidget); @@ -83,12 +88,13 @@ export class NotebookMergeWidget extends MergePanel { rendermime, mimetype: model.mimetype, editorFactory: this._editorFactory, + translator: this._translator, ...this._viewOptions, }); this.cellWidgets.push(w); if (c.onesided && c.conflicted) { if (chunk === null) { - chunk = new ChunkedCellsWidget(); + chunk = new ChunkedCellsWidget({ translator: this._translator }); chunk.cells.moved.connect(this.onDragDropMove, this); chunk.resolved.connect(this.onChunkResolved, this); this.cellContainer.addToFriendlyGroup(chunk.cells); @@ -203,11 +209,18 @@ export class NotebookMergeWidget extends MergePanel { * Collection of notebook-wide controls */ class NotebookMergeControls extends FlexPanel { - constructor(model: NotebookMergeModel) { + constructor({ + model, + translator, + }: { + model: NotebookMergeModel; + translator: ITranslator; + }) { super({ direction: 'left-to-right', }); this.model = model; + this.trans = translator.load('nbdime'); this.addClass(NB_MERGE_CONTROLS_CLASS); let anyOutputs = false; for (let cell of model.cells) { @@ -223,12 +236,12 @@ class NotebookMergeControls extends FlexPanel { init_controls(): void { // Add "Clear all outputs" checkbox - let chk = createCheckbox(false, 'Clear all cell outputs'); + let chk = createCheckbox(false, this.trans.__('Clear all cell outputs')); this.clearOutputsToggle = chk.checkbox; this.addWidget(chk.widget); // Add "Clear all conflicted outputs" checkbox - chk = createCheckbox(false, 'Clear conflicted cell outputs'); + chk = createCheckbox(false, this.trans.__('Clear conflicted cell outputs')); this.clearConflictedOutputsToggle = chk.checkbox; this.addWidget(chk.widget); @@ -353,4 +366,6 @@ class NotebookMergeControls extends FlexPanel { clearConflictedOutputsToggle: HTMLInputElement; model: NotebookMergeModel; + + protected trans: TranslationBundle; } diff --git a/ui-tests/tests/nbdime-merge-test4.spec.ts b/ui-tests/tests/nbdime-merge-test4.spec.ts index 6a90a2eb..31865409 100644 --- a/ui-tests/tests/nbdime-merge-test4.spec.ts +++ b/ui-tests/tests/nbdime-merge-test4.spec.ts @@ -10,7 +10,9 @@ test.beforeEach(async ({ page }) => { /* notebooks of same length and 1 conflict*/ test.describe('merge test4', () => { - test('should synchronize the collapse status between editor', async ({ page }) => { + test('should synchronize the collapse status between editor', async ({ + page, + }) => { expect.soft(await page.locator('#main').screenshot()).toMatchSnapshot(); // Should display 8 collapsers @@ -19,8 +21,9 @@ test.describe('merge test4', () => { const collapsers2 = page.getByText('5 unchanged lines'); await expect.soft(collapsers2).toHaveCount(4); await expect.soft(page.getByText('import numpy')).toHaveCount(0); - await expect.soft(page.getByText('noise = np.random.normal(0.0, 0.2, nx)')).toHaveCount(0); - + await expect + .soft(page.getByText('noise = np.random.normal(0.0, 0.2, nx)')) + .toHaveCount(0); // Click on the base editor collapsers await page.getByText('12 unchanged lines').nth(1).click(); @@ -31,7 +34,8 @@ test.describe('merge test4', () => { // Should not display any collapser await expect(page.getByText('import numpy')).toHaveCount(4); - await expect(page.getByText('noise = np.random.normal(0.0, 0.2, nx)')).toHaveCount(4); - + await expect( + page.getByText('noise = np.random.normal(0.0, 0.2, nx)'), + ).toHaveCount(4); }); }); diff --git a/ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-linux.png b/ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-linux.png index 7f857a23..a3914e2e 100644 Binary files a/ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-linux.png and b/ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-linux.png differ diff --git a/ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-win32.png b/ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-win32.png index f780199e..281cff89 100644 Binary files a/ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-win32.png and b/ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-win32.png differ