From b23190598151de44da441caa84580d2283540256 Mon Sep 17 00:00:00 2001 From: Nriver <6752679+Nriver@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:07:00 +0800 Subject: [PATCH 01/10] add translation for backup.js --- .../widgets/type_widgets/options/backup.js | 23 ++++++++++--------- src/public/translations/cn/translation.json | 17 ++++++++++++++ src/public/translations/en/translation.json | 13 +++++++++++ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/backup.js b/src/public/app/widgets/type_widgets/options/backup.js index 42b5e15ed..1be36366e 100644 --- a/src/public/app/widgets/type_widgets/options/backup.js +++ b/src/public/app/widgets/type_widgets/options/backup.js @@ -1,45 +1,46 @@ +import { t } from "../../../services/i18n.js"; import server from "../../../services/server.js"; import toastService from "../../../services/toast.js"; import OptionsWidget from "./options_widget.js"; const TPL = `
-

Automatic backup

+

${t('backup.automatic_backup')}

-

Trilium can back up the database automatically:

+

${t('backup.automatic_backup_description')}

-

It's recommended to keep the backup turned on, but this can make application startup slow with large databases and/or slow storage devices.

+

${t('backup.backup_recommendation')}

-

Backup now

+

${t('backup.backup_now')}

- +
-

Existing backups

+

${t('backup.existing_backups')}

@@ -54,7 +55,7 @@ export default class BackupOptions extends OptionsWidget { this.$backupDatabaseButton.on('click', async () => { const {backupFile} = await server.post('database/backup-database'); - toastService.showMessage(`Database has been backed up to ${backupFile}`, 10000); + toastService.showMessage(`${t('backup.database_backed_up_to')} ${backupFile}`, 10000); this.refresh(); }); @@ -84,7 +85,7 @@ export default class BackupOptions extends OptionsWidget { this.$existingBackupList.empty(); if (!backupFiles.length) { - backupFiles = [{filePath: "no backup yet", mtime: ''}]; + backupFiles = [{filePath: t('backup.no_backup_yet'), mtime: ''}]; } for (const {filePath, mtime} of backupFiles) { diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index c943e1fb1..35acbd982 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -1114,5 +1114,22 @@ "title": "自动只读大小", "description": "自动只读笔记大小是超过该大小后,笔记将以只读模式显示(出于性能考虑)。", "label": "自动只读大小(文本笔记)" + }, + "i18n": { + "title": "本地化", + "language": "语言" + }, + "backup": { + "automatic_backup": "自动备份", + "automatic_backup_description": "Trilium 可以自动备份数据库:", + "enable_daily_backup": "启用每日备份", + "enable_weekly_backup": "启用每周备份", + "enable_monthly_backup": "启用每月备份", + "backup_recommendation": "建议打开备份功能,但这可能会使大型数据库和/或慢速存储设备的应用程序启动变慢。", + "backup_now": "立即备份", + "backup_database_now": "立即备份数据库", + "existing_backups": "已有备份", + "database_backed_up_to": "数据库已备份到", + "no_backup_yet": "尚无备份" } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 07adb0c63..ced29aa37 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1119,5 +1119,18 @@ "i18n": { "title": "Localization", "language": "Language" + }, + "backup": { + "automatic_backup": "Automatic backup", + "automatic_backup_description": "Trilium can back up the database automatically:", + "enable_daily_backup": "Enable daily backup", + "enable_weekly_backup": "Enable weekly backup", + "enable_monthly_backup": "Enable monthly backup", + "backup_recommendation": "It's recommended to keep the backup turned on, but this can make application startup slow with large databases and/or slow storage devices.", + "backup_now": "Backup now", + "backup_database_now": "Backup database now", + "existing_backups": "Existing backups", + "database_backed_up_to": "Database has been backed up to", + "no_backup_yet": "no backup yet" } } From 68733cf0c29444adea5aae662ddab051d7bc048b Mon Sep 17 00:00:00 2001 From: Nriver <6752679+Nriver@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:31:00 +0800 Subject: [PATCH 02/10] add translation for 3 option files --- .../app/widgets/type_widgets/options/etapi.js | 41 ++++++++------- .../type_widgets/options/options_widget.js | 5 +- .../widgets/type_widgets/options/password.js | 34 ++++++------ src/public/translations/cn/translation.json | 52 ++++++++++++++++++- src/public/translations/en/translation.json | 50 ++++++++++++++++++ 5 files changed, 141 insertions(+), 41 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/etapi.js b/src/public/app/widgets/type_widgets/options/etapi.js index 58e956aa3..f2536842e 100644 --- a/src/public/app/widgets/type_widgets/options/etapi.js +++ b/src/public/app/widgets/type_widgets/options/etapi.js @@ -1,3 +1,4 @@ +import { t } from "../../../services/i18n.js"; import server from "../../../services/server.js"; import dialogService from "../../../services/dialog.js"; import toastService from "../../../services/toast.js"; @@ -5,24 +6,24 @@ import OptionsWidget from "./options_widget.js"; const TPL = `
-

ETAPI

+

${t("etapi.title")}

-

ETAPI is a REST API used to access Trilium instance programmatically, without UI.
- See more details on wiki and ETAPI OpenAPI spec.

+

${t("etapi.description")}
+ ${t("etapi.see_more")} ${t("etapi.wiki")} ${t("etapi.and")} ${t("etapi.openapi_spec")}.

- + -
Existing tokens
+
${t("etapi.existing_tokens")}
-
There are no tokens yet. Click on the button above to create one.
+
${t("etapi.no_tokens_yet")}
- - - + + + @@ -52,21 +53,21 @@ export default class EtapiOptions extends OptionsWidget { this.$widget.find(".create-etapi-token").on("click", async () => { const tokenName = await dialogService.prompt({ - title: "New ETAPI token", - message: "Please enter new token's name", - defaultValue: "new token" + title: t("etapi.new_token_title"), + message: t("etapi.new_token_message"), + defaultValue: t("etapi.default_token_name") }); if (!tokenName.trim()) { - toastService.showError("Token name can't be empty"); + toastService.showError(t("etapi.error_empty_name")); return; } const {authToken} = await server.post('etapi-tokens', {tokenName}); await dialogService.prompt({ - title: "ETAPI token created", - message: 'Copy the created token into clipboard. Trilium stores the token hashed and this is the last time you see it.', + title: t("etapi.token_created_title"), + message: t("etapi.token_created_message"), defaultValue: authToken }); @@ -94,9 +95,9 @@ export default class EtapiOptions extends OptionsWidget { .append($("
Token nameCreatedActions${t("etapi.token_name")}${t("etapi.created")}${t("etapi.actions")}
").text(token.name)) .append($("").text(token.utcDateCreated)) .append($("").append( - $('') + $('') .on("click", () => this.renameToken(token.etapiTokenId, token.name)), - $('') + $('') .on("click", () => this.deleteToken(token.etapiTokenId, token.name)) )) ); @@ -105,8 +106,8 @@ export default class EtapiOptions extends OptionsWidget { async renameToken(etapiTokenId, oldName) { const tokenName = await dialogService.prompt({ - title: "Rename token", - message: "Please enter new token's name", + title: t("etapi.rename_token_title"), + message: t("etapi.rename_token_message"), defaultValue: oldName }); @@ -120,7 +121,7 @@ export default class EtapiOptions extends OptionsWidget { } async deleteToken(etapiTokenId, name) { - if (!await dialogService.confirm(`Are you sure you want to delete ETAPI token "${name}"?`)) { + if (!await dialogService.confirm(t("etapi.delete_token_confirmation", { name }))) { return; } diff --git a/src/public/app/widgets/type_widgets/options/options_widget.js b/src/public/app/widgets/type_widgets/options/options_widget.js index 3f1630870..bfbcb8657 100644 --- a/src/public/app/widgets/type_widgets/options/options_widget.js +++ b/src/public/app/widgets/type_widgets/options/options_widget.js @@ -1,3 +1,4 @@ +import { t } from "../../../services/i18n.js"; import server from "../../../services/server.js"; import toastService from "../../../services/toast.js"; import NoteContextAwareWidget from "../../note_context_aware_widget.js"; @@ -24,8 +25,8 @@ export default class OptionsWidget extends NoteContextAwareWidget { showUpdateNotification() { toastService.showPersistent({ id: "options-change-saved", - title: "Options status", - message: "Options change have been saved.", + title: t("options_widget.options_status"), + message: t("options_widget.options_change_saved"), icon: "slider", closeAfter: 2000 }); diff --git a/src/public/app/widgets/type_widgets/options/password.js b/src/public/app/widgets/type_widgets/options/password.js index 7b42fb064..228e3ecd1 100644 --- a/src/public/app/widgets/type_widgets/options/password.js +++ b/src/public/app/widgets/type_widgets/options/password.js @@ -1,3 +1,4 @@ +import { t } from "../../../services/i18n.js"; import server from "../../../services/server.js"; import protectedSessionHolder from "../../../services/protected_session_holder.js"; import toastService from "../../../services/toast.js"; @@ -5,42 +6,39 @@ import OptionsWidget from "./options_widget.js"; const TPL = `
-

+

${t("password.heading")}

- +
- +
- +
- +
-

Protected Session Timeout

+

${t("password.protected_session_timeout")}

-

Protected session timeout is a time period after which the protected session is wiped from - the browser's memory. This is measured from the last interaction with protected notes. See wiki for more info.

+

${t("password.protected_session_timeout_description")} ${t("password.wiki")} ${t("password.for_more_info")}

- +
`; @@ -58,13 +56,13 @@ export default class PasswordOptions extends OptionsWidget { this.$resetPasswordButton = this.$widget.find(".reset-password-button"); this.$resetPasswordButton.on("click", async () => { - if (confirm("By resetting the password you will forever lose access to all your existing protected notes. Do you really want to reset the password?")) { + if (confirm(t("password.reset_confirmation"))) { await server.post("password/reset?really=yesIReallyWantToResetPasswordAndLoseAccessToMyProtectedNotes"); const options = await server.get('options'); this.optionsLoaded(options); - toastService.showError("Password has been reset. Please set new password"); + toastService.showError(t("password.reset_success_message")); } }); @@ -79,8 +77,8 @@ export default class PasswordOptions extends OptionsWidget { const isPasswordSet = options.isPasswordSet === 'true'; this.$widget.find(".old-password-form-group").toggle(isPasswordSet); - this.$passwordHeading.text(isPasswordSet ? 'Change Password' : 'Set Password'); - this.$savePasswordButton.text(isPasswordSet ? 'Change Password' : 'Set Password'); + this.$passwordHeading.text(isPasswordSet ? t("password.change_password_heading") : t("password.set_password_heading")); + this.$savePasswordButton.text(isPasswordSet ? t("password.change_password") : t("password.set_password")); this.$protectedSessionTimeout.val(options.protectedSessionTimeout); } @@ -94,7 +92,7 @@ export default class PasswordOptions extends OptionsWidget { this.$newPassword2.val(''); if (newPassword1 !== newPassword2) { - toastService.showError("New passwords are not the same."); + toastService.showError(t("password.password_mismatch")); return false; } @@ -103,7 +101,7 @@ export default class PasswordOptions extends OptionsWidget { 'new_password': newPassword1 }).then(result => { if (result.success) { - toastService.showError("Password has been changed. Trilium will be reloaded after you press OK."); + toastService.showError(t("password.password_changed_success")); // password changed so current protected session is invalid and needs to be cleared protectedSessionHolder.resetProtectedSession(); diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index 35acbd982..ba35e9a8f 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -1128,8 +1128,58 @@ "backup_recommendation": "建议打开备份功能,但这可能会使大型数据库和/或慢速存储设备的应用程序启动变慢。", "backup_now": "立即备份", "backup_database_now": "立即备份数据库", - "existing_backups": "已有备份", + "existing_backups": "现有备份", "database_backed_up_to": "数据库已备份到", "no_backup_yet": "尚无备份" + }, + "etapi": { + "title": "ETAPI", + "description": "ETAPI 是一个 REST API,用于以编程方式访问 Trilium 实例,而无需 UI。", + "see_more": "更多详情见", + "wiki": "维基", + "and": "和", + "openapi_spec": "ETAPI OpenAPI 规范", + "create_token": "创建新的 ETAPI 令牌", + "existing_tokens": "现有令牌", + "no_tokens_yet": "目前还没有令牌。点击上面的按钮创建一个。", + "token_name": "令牌名称", + "created": "创建时间", + "actions": "操作", + "new_token_title": "新 ETAPI 令牌", + "new_token_message": "请输入新的令牌名称", + "default_token_name": "新令牌", + "error_empty_name": "令牌名称不能为空", + "token_created_title": "ETAPI 令牌已创建", + "token_created_message": "将创建的令牌复制到剪贴板。Trilium 存储了令牌的哈希值,这是你最后一次看到它。", + "rename_token": "重命名此令牌", + "delete_token": "删除/停用此令牌", + "rename_token_title": "重命名令牌", + "rename_token_message": "请输入新的令牌名称", + "delete_token_confirmation": "你确定要删除 ETAPI 令牌 \"{{name}}\" 吗?" + }, + "options_widget": { + "options_status": "选项状态", + "options_change_saved": "选项更改已保存。" + }, + "password": { + "heading": "密码", + "alert_message": "请务必记住您的新密码。密码用于登录 Web 界面和加密保护的笔记。如果您忘记了密码,所有保护的笔记将永久丢失。", + "reset_link": "点击这里重置。", + "old_password": "旧密码", + "new_password": "新密码", + "new_password_confirmation": "新密码确认", + "change_password": "更改密码", + "protected_session_timeout": "保护会话超时", + "protected_session_timeout_description": "保护会话超时是一个时间段,超时后保护会话会从浏览器内存中清除。这是从最后一次与保护笔记的交互开始计时的。更多信息请见", + "wiki": "维基", + "for_more_info": "更多信息。", + "protected_session_timeout_label": "保护会话超时(秒)", + "reset_confirmation": "重置密码将永久丧失对所有现受保护笔记的访问。您真的要重置密码吗?", + "reset_success_message": "密码已重置。请设置新密码", + "change_password_heading": "更改密码", + "set_password_heading": "设置密码", + "set_password": "设置密码", + "password_mismatch": "新密码不一致。", + "password_changed_success": "密码已更改。按 OK 后 Trilium 将重新加载。" } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index ced29aa37..f36f6d93a 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1132,5 +1132,55 @@ "existing_backups": "Existing backups", "database_backed_up_to": "Database has been backed up to", "no_backup_yet": "no backup yet" + }, + "etapi": { + "title": "ETAPI", + "description": "ETAPI is a REST API used to access Trilium instance programmatically, without UI.", + "see_more": "See more details on", + "wiki": "wiki", + "and": "and", + "openapi_spec": "ETAPI OpenAPI spec", + "create_token": "Create new ETAPI token", + "existing_tokens": "Existing tokens", + "no_tokens_yet": "There are no tokens yet. Click on the button above to create one.", + "token_name": "Token name", + "created": "Created", + "actions": "Actions", + "new_token_title": "New ETAPI token", + "new_token_message": "Please enter new token's name", + "default_token_name": "new token", + "error_empty_name": "Token name can't be empty", + "token_created_title": "ETAPI token created", + "token_created_message": "Copy the created token into clipboard. Trilium stores the token hashed and this is the last time you see it.", + "rename_token": "Rename this token", + "delete_token": "Delete / deactivate this token", + "rename_token_title": "Rename token", + "rename_token_message": "Please enter new token's name", + "delete_token_confirmation": "Are you sure you want to delete ETAPI token \"{{name}}\"?" + }, + "options_widget": { + "options_status": "Options status", + "options_change_saved": "Options change have been saved." + }, + "password": { + "heading": "Password", + "alert_message": "Please take care to remember your new password. Password is used for logging into the web interface and to encrypt protected notes. If you forget your password, then all your protected notes are forever lost.", + "reset_link": "click here to reset it.", + "old_password": "Old password", + "new_password": "New password", + "new_password_confirmation": "New password confirmation", + "change_password": "Change password", + "protected_session_timeout": "Protected Session Timeout", + "protected_session_timeout_description": "Protected session timeout is a time period after which the protected session is wiped from the browser's memory. This is measured from the last interaction with protected notes. See", + "wiki": "wiki", + "for_more_info": "for more info.", + "protected_session_timeout_label": "Protected session timeout (in seconds)", + "reset_confirmation": "By resetting the password you will forever lose access to all your existing protected notes. Do you really want to reset the password?", + "reset_success_message": "Password has been reset. Please set new password", + "change_password_heading": "Change Password", + "set_password_heading": "Set Password", + "set_password": "Set Password", + "password_mismatch": "New passwords are not the same.", + "password_changed_success": "Password has been changed. Trilium will be reloaded after you press OK." } } From 78e908c761e8e5b3e8dd1b11cd2ff540b41891c7 Mon Sep 17 00:00:00 2001 From: Nriver <6752679+Nriver@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:12:33 +0800 Subject: [PATCH 03/10] add translation for shortcuts.js --- .../widgets/type_widgets/options/shortcuts.js | 41 ++++++++++--------- src/public/translations/cn/translation.json | 13 ++++++ src/public/translations/en/translation.json | 13 ++++++ 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/shortcuts.js b/src/public/app/widgets/type_widgets/options/shortcuts.js index 9430ba85f..19a6272c1 100644 --- a/src/public/app/widgets/type_widgets/options/shortcuts.js +++ b/src/public/app/widgets/type_widgets/options/shortcuts.js @@ -2,6 +2,7 @@ import server from "../../../services/server.js"; import utils from "../../../services/utils.js"; import dialogService from "../../../services/dialog.js"; import OptionsWidget from "./options_widget.js"; +import { t } from "../../../services/i18n.js"; const TPL = `
@@ -25,25 +26,25 @@ const TPL = ` } -

Keyboard Shortcuts

+

${t('shortcuts.keyboard_shortcuts')}

- Multiple shortcuts for the same action can be separated by comma. - See Electron documentation for available modifiers and key codes. + ${t('shortcuts.multiple_shortcuts')} + ${t('shortcuts.electron_documentation')}

- +
- - - - + + + + @@ -51,9 +52,9 @@ const TPL = `
- + - +
`; @@ -83,10 +84,10 @@ export default class KeyboardShortcutsOptions extends OptionsWidget { else { $tr.append($("
Action nameShortcutsDefault shortcutsDescription${t('shortcuts.action_name')}${t('shortcuts.shortcuts')}${t('shortcuts.default_shortcuts')}${t('shortcuts.description')}
").text(action.actionName)) .append($("").append( - $(``) - .val(action.effectiveShortcuts.join(", ")) - .attr('data-keyboard-action-name', action.actionName) - .attr('data-default-keyboard-shortcuts', action.defaultShortcuts.join(", ")) + $(``) + .val(action.effectiveShortcuts.join(", ")) + .attr('data-keyboard-action-name', action.actionName) + .attr('data-default-keyboard-shortcuts', action.defaultShortcuts.join(", ")) ) ) .append($("").text(action.defaultShortcuts.join(", "))) @@ -101,10 +102,10 @@ export default class KeyboardShortcutsOptions extends OptionsWidget { const $input = this.$widget.find(e.target); const actionName = $input.attr('data-keyboard-action-name'); const shortcuts = $input.val() - .replace('+,', "+Comma") - .split(",") - .map(shortcut => shortcut.replace("+Comma", "+,")) - .filter(shortcut => !!shortcut); + .replace('+,', "+Comma") + .split(",") + .map(shortcut => shortcut.replace("+Comma", "+,")) + .filter(shortcut => !!shortcut); const optionName = `keyboardShortcuts${actionName.substr(0, 1).toUpperCase()}${actionName.substr(1)}`; @@ -112,7 +113,7 @@ export default class KeyboardShortcutsOptions extends OptionsWidget { }); this.$widget.find(".options-keyboard-shortcuts-set-all-to-default").on('click', async () => { - if (!await dialogService.confirm("Do you really want to reset all keyboard shortcuts to the default?")) { + if (!await dialogService.confirm(t('shortcuts.confirm_reset'))) { return; } @@ -152,7 +153,7 @@ export default class KeyboardShortcutsOptions extends OptionsWidget { return; } - this.$widget.find(el).toggle(!!( // !! to avoid toggle overloads with different behavior + this.$widget.find(el).toggle(!!( action.actionName.toLowerCase().includes(filter) || action.defaultShortcuts.some(shortcut => shortcut.toLowerCase().includes(filter)) || action.effectiveShortcuts.some(shortcut => shortcut.toLowerCase().includes(filter)) diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index ba35e9a8f..2efb60d67 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -1181,5 +1181,18 @@ "set_password": "设置密码", "password_mismatch": "新密码不一致。", "password_changed_success": "密码已更改。按 OK 后 Trilium 将重新加载。" + }, + "shortcuts": { + "keyboard_shortcuts": "快捷键", + "multiple_shortcuts": "同一操作的多个快捷键可以用逗号分隔。", + "electron_documentation": "请参阅 Electron文档,了解可用的修饰符和键码。", + "type_text_to_filter": "输入文字以过滤快捷键...", + "action_name": "操作名称", + "shortcuts": "快捷键", + "default_shortcuts": "默认快捷键", + "description": "描述", + "reload_app": "重新加载应用以应用更改", + "set_all_to_default": "将所有快捷键重置为默认值", + "confirm_reset": "您确定要将所有键盘快捷键重置为默认值吗?" } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index f36f6d93a..1fc78b79e 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1182,5 +1182,18 @@ "set_password": "Set Password", "password_mismatch": "New passwords are not the same.", "password_changed_success": "Password has been changed. Trilium will be reloaded after you press OK." + }, + "shortcuts": { + "keyboard_shortcuts": "Keyboard Shortcuts", + "multiple_shortcuts": "Multiple shortcuts for the same action can be separated by comma.", + "electron_documentation": "See Electron documentation for available modifiers and key codes.", + "type_text_to_filter": "Type text to filter shortcuts...", + "action_name": "Action name", + "shortcuts": "Shortcuts", + "default_shortcuts": "Default shortcuts", + "description": "Description", + "reload_app": "Reload app to apply changes", + "set_all_to_default": "Set all shortcuts to the default", + "confirm_reset": "Do you really want to reset all keyboard shortcuts to the default?" } } From 9210f915cac1185a41d5a87a55b43bd33c2d9711 Mon Sep 17 00:00:00 2001 From: Nriver <6752679+Nriver@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:14:07 +0800 Subject: [PATCH 04/10] fix text wrap for shortcuts.js --- src/public/app/widgets/type_widgets/options/shortcuts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/app/widgets/type_widgets/options/shortcuts.js b/src/public/app/widgets/type_widgets/options/shortcuts.js index 19a6272c1..bbfa28933 100644 --- a/src/public/app/widgets/type_widgets/options/shortcuts.js +++ b/src/public/app/widgets/type_widgets/options/shortcuts.js @@ -40,7 +40,7 @@ const TPL = `
- + From a78859fc2aaffa0d1a3e2a71330951625b6d9b22 Mon Sep 17 00:00:00 2001 From: Nriver <6752679+Nriver@users.noreply.github.com> Date: Thu, 15 Aug 2024 11:26:51 +0800 Subject: [PATCH 05/10] add translation for the last 2 option files --- .../type_widgets/options/spellcheck.js | 15 +++++----- .../app/widgets/type_widgets/options/sync.js | 28 +++++++++---------- src/public/translations/cn/translation.json | 24 ++++++++++++++++ src/public/translations/en/translation.json | 24 ++++++++++++++++ 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/spellcheck.js b/src/public/app/widgets/type_widgets/options/spellcheck.js index 1f714ca0b..df2daa8e4 100644 --- a/src/public/app/widgets/type_widgets/options/spellcheck.js +++ b/src/public/app/widgets/type_widgets/options/spellcheck.js @@ -1,27 +1,28 @@ import utils from "../../../services/utils.js"; import OptionsWidget from "./options_widget.js"; +import { t } from "../../../services/i18n.js"; const TPL = `
-

Spell Check

+

${t('spellcheck.title')}

-

These options apply only for desktop builds, browsers will use their own native spell check. App restart is required after change.

+

${t('spellcheck.description')}


- - + +
-

Multiple languages can be separated by comma, e.g. en-US, de-DE, cs. Changes to the spell check options will take effect after application restart.

+

${t('spellcheck.multiple_languages_info')}

-

Available language codes:

+

${t('spellcheck.available_language_codes_label')}

`; export default class SpellcheckOptions extends OptionsWidget { diff --git a/src/public/app/widgets/type_widgets/options/sync.js b/src/public/app/widgets/type_widgets/options/sync.js index bb8e3c9dd..0b5bebf9d 100644 --- a/src/public/app/widgets/type_widgets/options/sync.js +++ b/src/public/app/widgets/type_widgets/options/sync.js @@ -1,44 +1,45 @@ import server from "../../../services/server.js"; import toastService from "../../../services/toast.js"; import OptionsWidget from "./options_widget.js"; +import { t } from "../../../services/i18n.js"; const TPL = `
-

Sync Configuration

+

${t('sync_2.config_title')}

- +
- +
- + -

Note: If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only).

-

Another special value is noproxy which forces ignoring even the system proxy and respectes NODE_TLS_REJECT_UNAUTHORIZED.

+

${t('sync_2.note')}: ${t('sync_2.note_description')}

+

${t('sync_2.special_value_description')}

- + - +
-

Sync Test

+

${t('sync_2.test_title')}

-

This will test the connection and handshake to the sync server. If the sync server isn't initialized, this will set it up to sync with the local document.

+

${t('sync_2.test_description')}

- +
`; export default class SyncOptions extends OptionsWidget { @@ -58,9 +59,8 @@ export default class SyncOptions extends OptionsWidget { if (result.success) { toastService.showMessage(result.message); - } - else { - toastService.showError(`Sync server handshake failed, error: ${result.message}`); + } else { + toastService.showError(t('sync_2.handshake_failed', { message: result.message })); } }); } diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index 2efb60d67..92e94c935 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -1194,5 +1194,29 @@ "reload_app": "重新加载应用以应用更改", "set_all_to_default": "将所有快捷键重置为默认值", "confirm_reset": "您确定要将所有键盘快捷键重置为默认值吗?" + }, + "spellcheck": { + "title": "拼写检查", + "description": "这些选项仅适用于桌面版本,浏览器将使用其原生的拼写检查功能。更改后需要重启应用。", + "enable": "启用拼写检查", + "language_code_label": "语言代码", + "language_code_placeholder": "例如 \"en-US\", \"de-AT\"", + "multiple_languages_info": "多种语言可以用逗号分隔,例如 \"en-US, de-DE, cs\"。拼写检查选项的更改将在应用重启后生效。", + "available_language_codes_label": "可用的语言代码:" + }, + "sync_2": { + "config_title": "同步配置", + "server_address": "服务器地址", + "timeout": "同步超时(单位:毫秒)", + "proxy_label": "同步代理服务器(可选)", + "note": "注意", + "note_description": "代理设置留空则使用系统代理(仅桌面客户端有效)。", + "special_value_description": "另一个特殊值是 noproxy,它强制忽略系统代理并遵守 NODE_TLS_REJECT_UNAUTHORIZED。", + "save": "保存", + "help": "帮助", + "test_title": "同步测试", + "test_description": "测试和同步服务器之间的连接。如果同步服务器没有初始化,会将本地文档同步到同步服务器上。", + "test_button": "测试同步", + "handshake_failed": "同步服务器握手失败,错误:{{message}}" } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 1fc78b79e..45c7058d0 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1195,5 +1195,29 @@ "reload_app": "Reload app to apply changes", "set_all_to_default": "Set all shortcuts to the default", "confirm_reset": "Do you really want to reset all keyboard shortcuts to the default?" + }, + "spellcheck": { + "title": "Spell Check", + "description": "These options apply only for desktop builds, browsers will use their own native spell check. App restart is required after change.", + "enable": "Enable spellcheck", + "language_code_label": "Language code(s)", + "language_code_placeholder": "for example \"en-US\", \"de-AT\"", + "multiple_languages_info": "Multiple languages can be separated by comma, e.g. \"en-US, de-DE, cs\". Changes to the spell check options will take effect after application restart.", + "available_language_codes_label": "Available language codes:" + }, + "sync": { + "config_title": "Sync Configuration", + "server_address": "Server instance address", + "timeout": "Sync timeout (milliseconds)", + "proxy_label": "Sync proxy server (optional)", + "note": "Note", + "note_description": "If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only).", + "special_value_description": "Another special value is noproxy which forces ignoring even the system proxy and respects NODE_TLS_REJECT_UNAUTHORIZED.", + "save": "Save", + "help": "Help", + "test_title": "Sync Test", + "test_description": "This will test the connection and handshake to the sync server. If the sync server isn't initialized, this will set it up to sync with the local document.", + "test_button": "Test sync", + "handshake_failed": "Sync server handshake failed, error: {{message}}" } } From 34d2f20e16f76ac2e9a8ca81d52fa6c2c282bb6e Mon Sep 17 00:00:00 2001 From: Nriver <6752679+Nriver@users.noreply.github.com> Date: Thu, 15 Aug 2024 11:31:11 +0800 Subject: [PATCH 06/10] fix translation key --- src/public/translations/en/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 45c7058d0..245c371b1 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1205,7 +1205,7 @@ "multiple_languages_info": "Multiple languages can be separated by comma, e.g. \"en-US, de-DE, cs\". Changes to the spell check options will take effect after application restart.", "available_language_codes_label": "Available language codes:" }, - "sync": { + "sync_2": { "config_title": "Sync Configuration", "server_address": "Server instance address", "timeout": "Sync timeout (milliseconds)", From 4c3694bfa379e122a7dd42b3108eb8d13530e115 Mon Sep 17 00:00:00 2001 From: Nriver <6752679+Nriver@users.noreply.github.com> Date: Thu, 15 Aug 2024 11:33:44 +0800 Subject: [PATCH 07/10] add translation for widget: api_log.js --- src/public/app/widgets/api_log.js | 3 ++- src/public/translations/cn/translation.json | 3 +++ src/public/translations/en/translation.json | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/api_log.js b/src/public/app/widgets/api_log.js index faa01f108..b1a85a486 100644 --- a/src/public/app/widgets/api_log.js +++ b/src/public/app/widgets/api_log.js @@ -1,3 +1,4 @@ +import { t } from "../services/i18n.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js"; const TPL = ` @@ -32,7 +33,7 @@ const TPL = ` } -
+
`; diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index 92e94c935..882ce1629 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -1218,5 +1218,8 @@ "test_description": "测试和同步服务器之间的连接。如果同步服务器没有初始化,会将本地文档同步到同步服务器上。", "test_button": "测试同步", "handshake_failed": "同步服务器握手失败,错误:{{message}}" + }, + "api_log": { + "close": "关闭" } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 245c371b1..8d9eaa5fa 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1219,5 +1219,8 @@ "test_description": "This will test the connection and handshake to the sync server. If the sync server isn't initialized, this will set it up to sync with the local document.", "test_button": "Test sync", "handshake_failed": "Sync server handshake failed, error: {{message}}" + }, + "api_log": { + "close": "Close" } } From e7eec0f32841d2bdf98a9c8ae98a865135b91a60 Mon Sep 17 00:00:00 2001 From: Nriver <6752679+Nriver@users.noreply.github.com> Date: Thu, 15 Aug 2024 11:42:30 +0800 Subject: [PATCH 08/10] add translation for widget: attachment_detail.js --- src/public/app/widgets/attachment_detail.js | 13 +++++++------ src/public/translations/cn/translation.json | 8 ++++++++ src/public/translations/en/translation.json | 8 ++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/public/app/widgets/attachment_detail.js b/src/public/app/widgets/attachment_detail.js index 8c034b8cd..63dc1ff7e 100644 --- a/src/public/app/widgets/attachment_detail.js +++ b/src/public/app/widgets/attachment_detail.js @@ -1,3 +1,4 @@ +import { t } from "../services/i18n.js"; import utils from "../services/utils.js"; import AttachmentActionsWidget from "./buttons/attachments_actions.js"; import BasicWidget from "./basic_widget.js"; @@ -153,19 +154,19 @@ export default class AttachmentDetailWidget extends BasicWidget { $deletionWarning.show(); if (willBeDeletedInMs >= 60000) { - $deletionWarning.text(`This attachment will be automatically deleted in ${utils.formatTimeInterval(willBeDeletedInMs)}`); + $deletionWarning.text(t('attachment_detail_2.will_be_deleted_in', { time: utils.formatTimeInterval(willBeDeletedInMs) })); } else { - $deletionWarning.text(`This attachment will be automatically deleted soon`); + $deletionWarning.text(t('attachment_detail_2.will_be_deleted_soon')); } - $deletionWarning.append(", because the attachment is not linked in the note's content. To prevent deletion, add the attachment link back into the content or convert the attachment into note."); + $deletionWarning.append(t('attachment_detail_2.deletion_reason')); } else { this.$wrapper.removeClass("scheduled-for-deletion"); $deletionWarning.hide(); } this.$wrapper.find('.attachment-details') - .text(`Role: ${this.attachment.role}, Size: ${utils.formatSize(this.attachment.contentLength)}`); + .text(t('attachment_detail_2.role_and_size', { role: this.attachment.role, size: utils.formatSize(this.attachment.contentLength) })); this.$wrapper.find('.attachment-actions-container').append(this.attachmentActionsWidget.render()); const {$renderedContent} = await contentRenderer.getRenderedContent(this.attachment, { imageHasZoom: this.isFullDetail }); @@ -186,9 +187,9 @@ export default class AttachmentDetailWidget extends BasicWidget { utils.copyHtmlToClipboard($link[0].outerHTML); - toastService.showMessage("Attachment link copied to clipboard."); + toastService.showMessage(t('attachment_detail_2.link_copied')); } else { - throw new Error(`Unrecognized attachment role '${this.attachment.role}'.`); + throw new Error(t('attachment_detail_2.unrecognized_role', { role: this.attachment.role })); } } diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index 882ce1629..349c5d396 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -1221,5 +1221,13 @@ }, "api_log": { "close": "关闭" + }, + "attachment_detail_2": { + "will_be_deleted_in": "此附件将在 {{time}} 后自动删除", + "will_be_deleted_soon": "该附件将很快被自动删除", + "deletion_reason": ",因为该附件未链接在笔记的内容中。为防止被删除,请将附件链接重新添加到内容中或将附件转换为笔记。", + "role_and_size": "角色: {{role}}, 大小: {{size}}", + "link_copied": "附件链接已复制到剪贴板。", + "unrecognized_role": "无法识别的附件角色 '{{role}}'。" } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 8d9eaa5fa..425ea4a26 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1222,5 +1222,13 @@ }, "api_log": { "close": "Close" + }, + "attachment_detail_2": { + "will_be_deleted_in": "This attachment will be automatically deleted in {time}}", + "will_be_deleted_soon": "This attachment will be automatically deleted soon", + "deletion_reason": ", because the attachment is not linked in the note's content. To prevent deletion, add the attachment link back into the content or convert the attachment into note.", + "role_and_size": "Role: {{role}}, Size: {{size}}", + "link_copied": "Attachment link copied to clipboard.", + "unrecognized_role": "Unrecognized attachment role '{{role}}'." } } From 0198f135ac327f316ec0011cf671c5a659d65053 Mon Sep 17 00:00:00 2001 From: Nriver <6752679+Nriver@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:27:22 +0800 Subject: [PATCH 09/10] Fix duplicated key and missing '{' in translation.json --- src/public/translations/en/translation.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 425ea4a26..6374c7fcf 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -536,7 +536,6 @@ "delete_success": "Attachment '{{title}}' has been deleted.", "convert_confirm": "Are you sure you want to convert attachment '{{title}}' into a separate note?", "convert_success": "Attachment '{{title}}' has been converted to note.", - "rename_attachment": "Rename attachment", "enter_new_name": "Please enter new attachment's name" }, "calendar": { @@ -1224,7 +1223,7 @@ "close": "Close" }, "attachment_detail_2": { - "will_be_deleted_in": "This attachment will be automatically deleted in {time}}", + "will_be_deleted_in": "This attachment will be automatically deleted in {{time}}", "will_be_deleted_soon": "This attachment will be automatically deleted soon", "deletion_reason": ", because the attachment is not linked in the note's content. To prevent deletion, add the attachment link back into the content or convert the attachment into note.", "role_and_size": "Role: {{role}}, Size: {{size}}", From 8e87bcf37b2c523fd3534a16347afb3409c617fc Mon Sep 17 00:00:00 2001 From: Nriver <6752679+Nriver@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:39:26 +0800 Subject: [PATCH 10/10] add translation for bookmark_switch.js and editability_select.js --- src/public/app/widgets/bookmark_switch.js | 9 +++-- src/public/app/widgets/editability_select.js | 41 ++++++++++---------- src/public/translations/cn/translation.json | 13 +++++++ src/public/translations/en/translation.json | 13 +++++++ 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/public/app/widgets/bookmark_switch.js b/src/public/app/widgets/bookmark_switch.js index 22040e3e8..ab9055df1 100644 --- a/src/public/app/widgets/bookmark_switch.js +++ b/src/public/app/widgets/bookmark_switch.js @@ -1,6 +1,7 @@ import SwitchWidget from "./switch.js"; import server from "../services/server.js"; import toastService from "../services/toast.js"; +import { t } from "../services/i18n.js"; export default class BookmarkSwitchWidget extends SwitchWidget { isEnabled() { @@ -12,11 +13,11 @@ export default class BookmarkSwitchWidget extends SwitchWidget { doRender() { super.doRender(); - this.$switchOnName.text("Bookmark"); - this.$switchOnButton.attr("title", "Bookmark this note to the left side panel"); + this.$switchOnName.text(t("bookmark_switch.bookmark")); + this.$switchOnButton.attr("title", t("bookmark_switch.bookmark_this_note")); - this.$switchOffName.text("Bookmark"); - this.$switchOffButton.attr("title", "Remove bookmark"); + this.$switchOffName.text(t("bookmark_switch.bookmark")); + this.$switchOffButton.attr("title", t("bookmark_switch.remove_bookmark")); } async toggle(state) { diff --git a/src/public/app/widgets/editability_select.js b/src/public/app/widgets/editability_select.js index 2cc4f14de..00460be18 100644 --- a/src/public/app/widgets/editability_select.js +++ b/src/public/app/widgets/editability_select.js @@ -1,5 +1,6 @@ import attributeService from '../services/attributes.js'; import NoteContextAwareWidget from "./note_context_aware_widget.js"; +import { t } from "../services/i18n.js"; const TPL = ` @@ -46,20 +47,20 @@ export default class EditabilitySelectWidget extends NoteContextAwareWidget { this.$widget.on('click', '.dropdown-item', async e => { - this.$widget.find('.dropdown-toggle').dropdown('toggle'); + this.$widget.find('.dropdown-toggle').dropdown('toggle'); - const editability = $(e.target).closest("[data-editability]").attr("data-editability"); + const editability = $(e.target).closest("[data-editability]").attr("data-editability"); - for (const ownedAttr of this.note.getOwnedLabels()) { - if (['readOnly', 'autoReadOnlyDisabled'].includes(ownedAttr.name)) { - await attributeService.removeAttributeById(this.noteId, ownedAttr.attributeId); + for (const ownedAttr of this.note.getOwnedLabels()) { + if (['readOnly', 'autoReadOnlyDisabled'].includes(ownedAttr.name)) { + await attributeService.removeAttributeById(this.noteId, ownedAttr.attributeId); + } } - } - if (editability !== 'auto') { - await attributeService.addLabel(this.noteId, editability); - } - }); + if (editability !== 'auto') { + await attributeService.addLabel(this.noteId, editability); + } + }); } async refreshWithNote(note) { @@ -73,9 +74,9 @@ export default class EditabilitySelectWidget extends NoteContextAwareWidget { } const labels = { - "auto": "Auto", - "readOnly": "Read-only", - "autoReadOnlyDisabled": "Always Editable" + "auto": t("editability_select.auto"), + "readOnly": t("editability_select.read_only"), + "autoReadOnlyDisabled": t("editability_select.always_editable") } this.$widget.find('.dropdown-item').removeClass("selected"); diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index 349c5d396..63b0aed5c 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -1229,5 +1229,18 @@ "role_and_size": "角色: {{role}}, 大小: {{size}}", "link_copied": "附件链接已复制到剪贴板。", "unrecognized_role": "无法识别的附件角色 '{{role}}'。" + }, + "bookmark_switch": { + "bookmark": "书签", + "bookmark_this_note": "将此笔记添加到左侧面板的书签", + "remove_bookmark": "移除书签" + }, + "editability_select": { + "auto": "自动", + "read_only": "只读", + "always_editable": "始终可编辑", + "note_is_editable": "笔记如果不太长则可编辑。", + "note_is_read_only": "笔记为只读,但可以通过点击按钮进行编辑。", + "note_is_always_editable": "无论笔记长度如何,始终可编辑。" } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 6374c7fcf..6e61f7634 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1229,5 +1229,18 @@ "role_and_size": "Role: {{role}}, Size: {{size}}", "link_copied": "Attachment link copied to clipboard.", "unrecognized_role": "Unrecognized attachment role '{{role}}'." + }, + "bookmark_switch": { + "bookmark": "Bookmark", + "bookmark_this_note": "Bookmark this note to the left side panel", + "remove_bookmark": "Remove bookmark" + }, + "editability_select": { + "auto": "Auto", + "read_only": "Read-only", + "always_editable": "Always Editable", + "note_is_editable": "Note is editable if it's not too long.", + "note_is_read_only": "Note is read-only, but can be edited with a button click.", + "note_is_always_editable": "Note is always editable, regardless of its length." } }
${t('shortcuts.action_name')} ${t('shortcuts.shortcuts')} ${t('shortcuts.default_shortcuts')}