Skip to content

Commit 5cb2bf7

Browse files
committed
frontend/i18n: bugfixing; more on jupyter and "new"
1 parent 13063e4 commit 5cb2bf7

23 files changed

+1531
-187
lines changed

src/packages/frontend/account/account-page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,12 @@ export const AccountPage: React.FC = () => {
265265

266266
function renderI18N(): JSX.Element | null {
267267
if (
268+
i18n_enabled == null ||
268269
i18n_enabled.isEmpty() ||
269270
(i18n_enabled.size === 1 && i18n_enabled.includes("en"))
270-
)
271+
) {
271272
return null;
273+
}
272274

273275
const i18n: Locale = getLocale(other_settings);
274276

src/packages/frontend/account/sign-out.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export const SignOut: React.FC<Props> = (props: Readonly<Props>) => {
9393
},
9494
{ everywhere },
9595
)}
96-
cancelText={intl.formatMessage(labels.button_cancel)}
96+
cancelText={intl.formatMessage(labels.cancel)}
9797
>
9898
<Button
9999
icon={<LogoutOutlined />}

src/packages/frontend/app/render.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ import { createRoot } from "react-dom/client";
99
import {
1010
redux,
1111
Redux,
12-
useEffect,
12+
useAsyncEffect,
1313
useTypedRedux,
1414
} from "@cocalc/frontend/app-framework";
1515
import {
1616
getLocale,
17-
Locale,
1817
LOCALIZATIONS,
1918
OTHER_SETTINGS_LOCALE_KEY,
2019
} from "@cocalc/frontend/i18n";
@@ -28,13 +27,19 @@ function App({ children }) {
2827
const { setLocale } = useLocalizationCtx();
2928
const other_settings = useTypedRedux("account", "other_settings");
3029

31-
useEffect(() => {
30+
// setting via ?lang=[locale] takes precedece over account settings
31+
useAsyncEffect(async () => {
3232
const lang = QueryParams.get("lang");
3333
if (lang != null) {
3434
if (lang in LOCALIZATIONS) {
3535
console.warn(
3636
`URL query parameter 'lang=${lang}' – overriding user configuration.`,
3737
);
38+
const store = redux.getStore("account");
39+
// we have to ensure the account store is available, because this code runs very early
40+
await store.async_wait({
41+
until: () => store.get_account_id() != null,
42+
});
3843
redux
3944
.getActions("account")
4045
.set_other_settings(OTHER_SETTINGS_LOCALE_KEY, lang);
@@ -45,11 +50,12 @@ function App({ children }) {
4550
`Known values: ${Object.keys(LOCALIZATIONS)}`,
4651
);
4752
}
53+
// removing the parameter, otherwise this conflicts with further changes of account settings
54+
QueryParams.remove("lang");
4855
} else {
49-
const i18n: Locale = getLocale(other_settings);
50-
setLocale(i18n);
56+
setLocale(getLocale(other_settings));
5157
}
52-
}, [other_settings]);
58+
}, [getLocale(other_settings)]);
5359

5460
return <AppContext.Provider value={appState}>{children}</AppContext.Provider>;
5561
}

src/packages/frontend/customize.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export interface CustomizeState {
172172
default_llm?: string;
173173
user_defined_llm: boolean;
174174

175-
i18n: List<Locale>;
175+
i18n?: List<Locale>;
176176
}
177177

178178
export class CustomizeStore extends Store<CustomizeState> {

src/packages/frontend/frame-editors/frame-tree/commands/generic-commands.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -620,10 +620,7 @@ addCommands({
620620

621621
word_count: {
622622
group: "get-info",
623-
label: defineMessage({
624-
id: "command.generic.word_count.label",
625-
defaultMessage: "Word Count",
626-
}),
623+
label: labels.word_count,
627624
title: defineMessage({
628625
id: "command.generic.word_count.title",
629626
defaultMessage:
@@ -1085,7 +1082,7 @@ addCommands({
10851082
})}
10861083
</div>
10871084
),
1088-
cancelText: intl.formatMessage(labels.button_cancel),
1085+
cancelText: intl.formatMessage(labels.cancel),
10891086
okText: intl.formatMessage({
10901087
id: "commands.generic.toggle_button_bar.confirm.ok",
10911088
defaultMessage: "Hide Menu Toolbar",

src/packages/frontend/frame-editors/latex-editor/editor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Spec for editing LaTeX documents.
88
*/
99

1010
import { IS_IOS, IS_IPAD } from "@cocalc/frontend/feature";
11-
import { editor } from "@cocalc/frontend/i18n";
11+
import { editor, labels } from "@cocalc/frontend/i18n";
1212
import { set } from "@cocalc/util/misc";
1313
import { CodemirrorEditor } from "../code-editor/codemirror-editor";
1414
import { createEditor } from "../frame-tree/editor";
@@ -159,8 +159,8 @@ const EDITOR_SPEC = {
159159
} as EditorDescription,
160160

161161
word_count: {
162-
short: "Word Count",
163-
name: "Word Count",
162+
short: labels.word_count,
163+
name: labels.word_count,
164164
icon: "file-alt",
165165
commands: set(["word_count"]),
166166
component: LatexWordCount,

src/packages/frontend/i18n/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,13 @@ CoCalc specific rules for implementing translations, of which I think are good t
6767
- **Explicit ID**: Technically, the ID is optional. Then it is computed as a hash upon extraction. However, this has two negative sides:
6868
- If the message changes, it's hash changes, and you have to start over with the translation. This is good from an idealistic standpoint, but if you just tweak a word or correct a typo, the existing translations are still ok. If the meaning changes completely, it's better to create a new ID. (Of course, changes to the `defaultMessage` need to go through the `extract → upload` step, except that the English translation uses the `defaultMessage` directly.)
6969
- Sorting: All the translations and also online tools like SimpleLocalize sort the translations by their keys. Look at the translated `i18n/de_DE.json` and you'll see that messages that are related are also next to each other. This also makes it possible to filter for a specific
70+
- Never reference an ID directly – always reference the object of the "defined message", such that label changes (for updating the translation) or typos do not cause problems.
71+
- Extracting the IDs check for duplicates, so, no worries about that.
7072
- Pitfall **No variables in properties**: I think the extraction process does not know how to deal with variables, when extracting strings from properties. So, either define the message objects elsewhere (like it is done with `labels`) or write a multiline string in place. See the examples below for what works.
7173
- **No `en` translation**: English is the default. The `defaultMessage` is already in the code. We do not download the supposedly translated `en` file and just let the fallback mechanism kick in. This also means that changes to the `defaultMessage` will show up with the next build, even without touching any of the translations.
7274
- **richTextElements**: in `app/localize.tsx`, a few default `richTextElements` are defined – just for convenience. Anchor tags must be defined individually, because link text and href can't be wrapped that way.
7375
- **Query parameter**: A new `?lang=en` (or `=de`, `=zh`, ...) query parameters lets you change the language as well. This also changes the account setting. Hence, a URL with `?lang=en` can be used to reset the account setting to English.
76+
- **Descriptions**: add descriptions, especially for jupyter notebooks or latex, to add more context. The description is not only shown in the translation tool, but also passed on to the language model doing the automatic translations.
7477

7578
## Style
7679

src/packages/frontend/i18n/common.ts

Lines changed: 191 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { defineMessages } from "react-intl";
22

33
export const labels = defineMessages({
4-
button_cancel: {
4+
cancel: {
55
id: "labels.button.cancel",
66
defaultMessage: "Cancel",
77
description:
@@ -200,7 +200,7 @@ export const labels = defineMessages({
200200
copy: { id: "labels.copy", defaultMessage: "Copy" },
201201
undo: { id: "labels.undo", defaultMessage: "Undo" },
202202
paste: { id: "labels.paste", defaultMessage: "Paste" },
203-
redo: { id: "labels.redo", defaultMessage: "redo" },
203+
redo: { id: "labels.redo", defaultMessage: "Redo" },
204204
reload_title: {
205205
id: "labels.reload_title",
206206
defaultMessage: "Reload this document",
@@ -222,6 +222,42 @@ export const labels = defineMessages({
222222
defaultMessage: "Stop",
223223
description: "Label on a button to stop an ongoing process",
224224
},
225+
restart: {
226+
id: "labels.restart",
227+
defaultMessage: "Restart",
228+
description:
229+
"Label on a button to restart a job, or Jupyter Notebook kernel, etc.",
230+
},
231+
validate: {
232+
id: "labels.validate",
233+
defaultMessage: "Validate",
234+
},
235+
clear: {
236+
id: "labels.clear",
237+
defaultMessage: "Clear",
238+
description: "Clean or clear something out, such that it empty",
239+
},
240+
word_count: {
241+
id: "labels.word_count",
242+
defaultMessage: "Word Count",
243+
description: "Tool to count words in a document",
244+
},
245+
latex_document: {
246+
id: "labels.latex_document",
247+
defaultMessage: "LaTeX Document",
248+
description:
249+
"Indicating a LaTeX Documents on a button label or frame title",
250+
},
251+
sagemath_worksheet: {
252+
id: "labels.sagemath_worksheet",
253+
defaultMessage: "SageMath Worksheet",
254+
description: "A SageMath Worksheet label on a button or frame title",
255+
},
256+
linux_terminal: {
257+
id: "labels.linux_terminal",
258+
defaultMessage: "Linux Terminal",
259+
description: "On a label or frame title describing a Linux Terminal"
260+
}
225261
});
226262

227263
export const menu = defineMessages({
@@ -435,6 +471,22 @@ export const jupyter = {
435471
},
436472
}),
437473
commands: defineMessages({
474+
insert_cell_above: {
475+
id: "jupyter.commands.insert_cell_above",
476+
defaultMessage: "Insert Cell Above",
477+
},
478+
insert_cell_below: {
479+
id: "jupyter.commands.insert_cell_below",
480+
defaultMessage: "Insert Cell Below",
481+
},
482+
enter_command_mode: {
483+
id: "jupyter.commands.enter_command_mode",
484+
defaultMessage: "Enter command mode",
485+
},
486+
enter_edit_mode: {
487+
id: "jupyter.commands.enter_edit_mode",
488+
defaultMessage: "Enter edit mode",
489+
},
438490
toggle_all_line_numbers: {
439491
id: "jupyter.commands.toggle_all_line_numbers",
440492
defaultMessage: "Toggle Line Numbers of All Cells",
@@ -448,6 +500,11 @@ export const jupyter = {
448500
defaultMessage: "Change Kernel...",
449501
description: "Change the Kernel in the Jupyter Notebook",
450502
},
503+
close_and_halt_menu: {
504+
id: "jupyter.commands.close_and_halt.menu",
505+
defaultMessage: "Close and halt",
506+
description: "Close and halt the Kernel and Jupyter Notebook",
507+
},
451508
change_kernel_title: {
452509
id: "jupyter.commands.change_kernel_title",
453510
defaultMessage: "Select from any of the available kernels.",
@@ -464,16 +521,62 @@ export const jupyter = {
464521
defaultMessage: "None",
465522
description: "Jupyter Notebook cell toolbar 'None' hides the toolbar",
466523
},
524+
525+
restart_kernel_noconf_menu: {
526+
id: "jupyter.commands.restart_kernel_noconf.menu",
527+
defaultMessage: "Restart kernel",
528+
description: "Restart Kernel of a Jupyter Notebook",
529+
},
530+
restart_kernel_clear_noconf_menu: {
531+
id: "jupyter.commands.restart_kernel_clear_noconf.menu",
532+
defaultMessage: "Restart kernel and clear output",
533+
description: "Restart Kernel of a Jupyter Notebook and clear all output",
534+
},
535+
restart_kernel_label: {
536+
id: "jupyter.commands.restart_kernel.label",
537+
defaultMessage: "Restart Kernel...",
538+
description: "Restart Kernel of a Jupyter Notebook",
539+
},
540+
restart_kernel_button: {
541+
id: "jupyter.commands.restart_kernel.button",
542+
defaultMessage: "Kernel",
543+
description: "Restart Kernel of a Jupyter Notebook",
544+
},
467545
restart_kernel_run_all_cells: {
468546
id: "jupyter.commands.restart_kernel_run_all_cells",
469547
defaultMessage: "Restart and Run All Cells...",
470548
describe: "In a Jupyter Notebook, restart kernel and run all cells",
471549
},
472550
restart_kernel_run_all_cells_button: {
473-
id: "jupyter.commands.restart_kernel_run_all_cells_button",
551+
id: "jupyter.commands.restart_kernel_run_all_cells.button",
552+
defaultMessage: "Run all...",
553+
describe: "In a Jupyter Notebook, restart kernel and run all cells",
554+
},
555+
restart_kernel_run_all_cells_noconf: {
556+
id: "jupyter.commands.restart_kernel_run_all_cells_noconf",
557+
defaultMessage: "Restart Kernel and Run All Cells",
558+
describe: "In a Jupyter Notebook, restart kernel and run all cells",
559+
},
560+
restart_kernel_run_all_cells_noconf_button: {
561+
id: "jupyter.commands.restart_kernel_run_all_cells_noconf_button",
474562
defaultMessage: "Run All",
475563
describe: "In a Jupyter Notebook, restart kernel and run all cells",
476564
},
565+
restart_kernel_run_all_cells_menu: {
566+
id: "jupyter.commands.restart_kernel_run_all_cells_menu",
567+
defaultMessage: "Run all...",
568+
describe: "In a Jupyter Notebook, restart kernel and run all cells",
569+
},
570+
restart_kernel_run_all_cells_without_halting: {
571+
id: "jupyter.commands.restart_kernel_run_all_cells_without_halting",
572+
defaultMessage: "Restart and Run All (do not stop on errors)...",
573+
describe: "In a Jupyter Notebook, restart kernel and run all cells",
574+
},
575+
restart_kernel_clear_output_menu: {
576+
id: "jupyter.commands.restart_kernel_clear_output.menu",
577+
defaultMessage: "Restart Kernel and Clear All Outputs...",
578+
describe: "In a Jupyter Notebook, restart kernel and clear output",
579+
},
477580
run_cell_and_insert_below: {
478581
id: "jupyter.commands.run_cell_and_insert_below",
479582
defaultMessage: "Run Selected Cells and Insert Below",
@@ -508,7 +611,91 @@ export const jupyter = {
508611
interrupt_kernel: {
509612
id: "jupyter.commands.interrupt_kernel",
510613
defaultMessage: "Interrupt Kernel (Stop)",
511-
description: "In a Jupyter Notebook, interrup the running kernel",
614+
description: "In a Jupyter Notebook, interrupt the running kernel",
615+
},
616+
shutdown_kernel_button: {
617+
id: "jupyter.commands.shutdown_kernel.button",
618+
defaultMessage: "Off",
619+
description: "In a Jupyter Notebook, turn the running kernel off",
620+
},
621+
shutdown_kernel_menu: {
622+
id: "jupyter.commands.shutdown_kernel.menu",
623+
defaultMessage: "Shutdown Kernel...",
624+
description: "In a Jupyter Notebook, turn the running kernel off",
625+
},
626+
shutdown_kernel_confirm_title: {
627+
id: "jupyter.commands.shutdown_kernel.title",
628+
defaultMessage: "Shutdown kernel?",
629+
description: "In a Jupyter Notebook",
630+
},
631+
shutdown_kernel_confirm_body: {
632+
id: "jupyter.commands.shutdown_kernel.body",
633+
defaultMessage:
634+
"Do you want to shutdown the current kernel? All variable values will be lost.",
635+
description: "In a Jupyter Notebook",
636+
},
637+
shutdown_kernel_confirm_label_shutdown: {
638+
id: "jupyter.commands.shutdown_kernel.label.shutdown",
639+
defaultMessage: "Shutdown",
640+
description: "Shutting down a Kernel of a Jupyter Notebook",
641+
},
642+
shutdown_kernel_confirm_label_continue: {
643+
id: "jupyter.commands.shutdown_kernel.label.continue",
644+
defaultMessage: "Continue running",
645+
description: "Continue running the Kernel of a Jupyter Notebook",
646+
},
647+
halt_kernel_menu: {
648+
id: "jupyter.commands.halt_kernel_menu.menu",
649+
defaultMessage: "Halt kernel...",
650+
description: "Halting a Kernel of a Jupyter Notebook",
651+
},
652+
validate_label: {
653+
id: "jupyter.commands.validate.label",
654+
defaultMessage: "Validate",
655+
description: "Validate a Jupyter Notebook",
656+
},
657+
validate_tooltip: {
658+
id: "jupyter.commands.validate.tooltip",
659+
defaultMessage:
660+
"Restart Jupyter Notebook and run all cells to validate that it works.",
661+
description: "Validate a Jupyter Notebook",
662+
},
663+
validate_title: {
664+
id: "jupyter.commands.validate.title",
665+
defaultMessage: "Validate notebook?",
666+
description: "Validate a Jupyter Notebook",
667+
},
668+
validate_body: {
669+
id: "jupyter.commands.validate.body",
670+
defaultMessage:
671+
"Validating the notebook will restart the kernel and run all cells in order, even those with errors. This will ensure that all output is exactly what results from running all cells in order.",
672+
description: "Validate a Jupyter Notebook",
673+
},
674+
refresh_kernels: {
675+
id: "jupyter.commands.refresh_kernels",
676+
defaultMessage: "Refresh Kernel List",
677+
description: "Reload list of all Kernels for Jupyter Notebooks",
678+
},
679+
refresh_kernels_tooltip: {
680+
id: "jupyter.commands.refresh_kernels.tooltip",
681+
defaultMessage:
682+
"Reload list of all available Kernels for running Jupyter Notebooks",
683+
},
684+
run_all_cells_menu: {
685+
id: "jupyter.commands.run_all_cells.menu",
686+
defaultMessage: "Run All Cells",
687+
description: "Run all cells in a Jupyter Notebook",
688+
},
689+
run_all_cells_above_menu: {
690+
id: "jupyter.commands.run_all_cells_above.menu",
691+
defaultMessage: "Run All Above Selected Cell",
692+
description: "Run all cells above selected cell in a Jupyter Notebook",
693+
},
694+
run_all_cells_below_menu: {
695+
id: "jupyter.commands.run_all_cells_below.menu",
696+
defaultMessage: "Run Selected Cell and All Below",
697+
description:
698+
"Run selected cell and all cells below selected cell in a Jupyter Notebook",
512699
},
513700
}),
514701
};

src/packages/frontend/i18n/components.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import { FormattedMessage } from "react-intl";
33
import { labels } from "./common";
44

55
export function CancelText() {
6-
return <FormattedMessage {...labels.button_cancel} />;
6+
return <FormattedMessage {...labels.cancel} />;
77
}

0 commit comments

Comments
 (0)