mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge pull request #342 from TriliumNext/feature/i18n-part2
i18n support (part 2)
This commit is contained in:
commit
ef955a300a
8
.idea/codeStyles/Project.xml
generated
8
.idea/codeStyles/Project.xml
generated
@ -6,8 +6,10 @@
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</value>
|
||||
</option>
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="USE_EXPLICIT_JS_EXTENSION" value="TRUE" />
|
||||
</JSCodeStyleSettings>
|
||||
<codeStyleSettings language="JSON">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
@ -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 = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="bx bx-x close-api-log-button" title="Close"></div>
|
||||
<div class="bx bx-x close-api-log-button" title="${t('api_log.close')}"></div>
|
||||
|
||||
<div class="api-log-container"></div>
|
||||
</div>`;
|
||||
|
@ -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 }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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 = `
|
||||
<div class="dropdown editability-select-widget">
|
||||
@ -15,24 +16,24 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle editability-button">
|
||||
<span class="editability-active-desc">auto</span>
|
||||
<span class="editability-active-desc">${t("editability_select.auto")}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="editability-dropdown dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" href="#" data-editability="auto">
|
||||
<span class="check">✓</span>
|
||||
Auto
|
||||
<div>Note is editable if it's not too long.</div>
|
||||
${t("editability_select.auto")}
|
||||
<div>${t("editability_select.note_is_editable")}</div>
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" data-editability="readOnly">
|
||||
<span class="check">✓</span>
|
||||
Read-only
|
||||
<div>Note is read-only, but can be edited with a button click.</div>
|
||||
${t("editability_select.read_only")}
|
||||
<div>${t("editability_select.note_is_read_only")}</div>
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" data-editability="autoReadOnlyDisabled">
|
||||
<span class="check">✓</span>
|
||||
Always editable
|
||||
<div>Note is always editable, regardless of its length.</div>
|
||||
${t("editability_select.always_editable")}
|
||||
<div>${t("editability_select.note_is_always_editable")}</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -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");
|
||||
|
@ -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 = `
|
||||
<div class="options-section">
|
||||
<h4>Automatic backup</h4>
|
||||
<h4>${t('backup.automatic_backup')}</h4>
|
||||
|
||||
<p>Trilium can back up the database automatically:</p>
|
||||
<p>${t('backup.automatic_backup_description')}</p>
|
||||
|
||||
<ul style="list-style: none">
|
||||
<li>
|
||||
<label>
|
||||
<input type="checkbox" class="daily-backup-enabled">
|
||||
Enable daily backup
|
||||
${t('backup.enable_daily_backup')}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="checkbox" class="weekly-backup-enabled">
|
||||
Enable weekly backup
|
||||
${t('backup.enable_weekly_backup')}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="checkbox" class="monthly-backup-enabled">
|
||||
Enable monthly backup
|
||||
${t('backup.enable_monthly_backup')}
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>It's recommended to keep the backup turned on, but this can make application startup slow with large databases and/or slow storage devices.</p>
|
||||
<p>${t('backup.backup_recommendation')}</p>
|
||||
</div>
|
||||
|
||||
<div class="options-section">
|
||||
<h4>Backup now</h4>
|
||||
<h4>${t('backup.backup_now')}</h4>
|
||||
|
||||
<button class="backup-database-button btn">Backup database now</button>
|
||||
<button class="backup-database-button btn">${t('backup.backup_database_now')}</button>
|
||||
</div>
|
||||
|
||||
<div class="options-section">
|
||||
<h4>Existing backups</h4>
|
||||
<h4>${t('backup.existing_backups')}</h4>
|
||||
|
||||
<ul class="existing-backup-list"></ul>
|
||||
</div>
|
||||
@ -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) {
|
||||
|
@ -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 = `
|
||||
<div class="options-section">
|
||||
<h4>ETAPI</h4>
|
||||
<h4>${t("etapi.title")}</h4>
|
||||
|
||||
<p>ETAPI is a REST API used to access Trilium instance programmatically, without UI. <br/>
|
||||
See more details on <a href="https://triliumnext.github.io/Docs/Wiki/etapi.html">wiki</a> and <a onclick="window.open('etapi/etapi.openapi.yaml')" href="etapi/etapi.openapi.yaml">ETAPI OpenAPI spec</a>.</p>
|
||||
<p>${t("etapi.description")} <br/>
|
||||
${t("etapi.see_more")} <a href="https://triliumnext.github.io/Docs/Wiki/etapi.html">${t("etapi.wiki")}</a> ${t("etapi.and")} <a onclick="window.open('etapi/etapi.openapi.yaml')" href="etapi/etapi.openapi.yaml">${t("etapi.openapi_spec")}</a>.</p>
|
||||
|
||||
<button type="button" class="create-etapi-token btn btn-sm">Create new ETAPI token</button>
|
||||
<button type="button" class="create-etapi-token btn btn-sm">${t("etapi.create_token")}</button>
|
||||
|
||||
<h5>Existing tokens</h5>
|
||||
<h5>${t("etapi.existing_tokens")}</h5>
|
||||
|
||||
<div class="no-tokens-yet">There are no tokens yet. Click on the button above to create one.</div>
|
||||
<div class="no-tokens-yet">${t("etapi.no_tokens_yet")}</div>
|
||||
|
||||
<div style="overflow: auto; height: 500px;">
|
||||
<table class="tokens-table table table-stripped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Token name</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
<th>${t("etapi.token_name")}</th>
|
||||
<th>${t("etapi.created")}</th>
|
||||
<th>${t("etapi.actions")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
@ -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($("<td>").text(token.name))
|
||||
.append($("<td>").text(token.utcDateCreated))
|
||||
.append($("<td>").append(
|
||||
$('<span class="bx bx-pen token-table-button" title="Rename this token"></span>')
|
||||
$('<span class="bx bx-pen token-table-button" title="${t("etapi.rename_token")}"></span>')
|
||||
.on("click", () => this.renameToken(token.etapiTokenId, token.name)),
|
||||
$('<span class="bx bx-trash token-table-button" title="Delete / deactivate this token"></span>')
|
||||
$('<span class="bx bx-trash token-table-button" title="${t("etapi.delete_token")}"></span>')
|
||||
.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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -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 = `
|
||||
<div class="options-section">
|
||||
<h4 class="password-heading"></h4>
|
||||
<h4 class="password-heading">${t("password.heading")}</h4>
|
||||
|
||||
<div class="alert alert-warning" role="alert" style="font-weight: bold; color: red !important;">
|
||||
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.
|
||||
In case you did forget your password, <a class="reset-password-button" href="javascript:">click here to reset it</a>.
|
||||
${t("password.alert_message")} <a class="reset-password-button" href="javascript:">${t("password.reset_link")}</a>
|
||||
</div>
|
||||
|
||||
<form class="change-password-form">
|
||||
<div class="old-password-form-group form-group">
|
||||
<label>Old password</label>
|
||||
<label>${t("password.old_password")}</label>
|
||||
<input class="old-password form-control" type="password">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>New password</label>
|
||||
<label>${t("password.new_password")}</label>
|
||||
<input class="new-password1 form-control" type="password">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>New password confirmation</label>
|
||||
<label>${t("password.new_password_confirmation")}</label>
|
||||
<input class="new-password2 form-control" type="password">
|
||||
</div>
|
||||
|
||||
<button class="save-password-button btn btn-primary">Change password</button>
|
||||
<button class="save-password-button btn btn-primary">${t("password.change_password")}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="options-section">
|
||||
<h4>Protected Session Timeout</h4>
|
||||
<h4>${t("password.protected_session_timeout")}</h4>
|
||||
|
||||
<p>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 <a href="https://triliumnext.github.io/Docs/Wiki/protected-notes.html" class="external">wiki</a> for more info.</p>
|
||||
<p>${t("password.protected_session_timeout_description")} <a href="https://triliumnext.github.io/Docs/Wiki/protected-notes.html" class="external">${t("password.wiki")}</a> ${t("password.for_more_info")}</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Protected session timeout (in seconds)</label>
|
||||
<label>${t("password.protected_session_timeout_label")}</label>
|
||||
<input class="protected-session-timeout-in-seconds form-control options-number-input" type="number" min="60">
|
||||
</div>
|
||||
</div>`;
|
||||
@ -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();
|
||||
|
@ -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 = `
|
||||
<div class="options-section shortcuts-options-section">
|
||||
@ -25,25 +26,25 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<h4>Keyboard Shortcuts</h4>
|
||||
<h4>${t('shortcuts.keyboard_shortcuts')}</h4>
|
||||
|
||||
<p>
|
||||
Multiple shortcuts for the same action can be separated by comma.
|
||||
See <a href="https://www.electronjs.org/docs/latest/api/accelerator">Electron documentation</a> for available modifiers and key codes.
|
||||
${t('shortcuts.multiple_shortcuts')}
|
||||
${t('shortcuts.electron_documentation')}
|
||||
</p>
|
||||
|
||||
<div class="form-group">
|
||||
<input type="text" class="keyboard-shortcut-filter form-control" placeholder="Type text to filter shortcuts...">
|
||||
<input type="text" class="keyboard-shortcut-filter form-control" placeholder="${t('shortcuts.type_text_to_filter')}">
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table-container">
|
||||
<table class="keyboard-shortcut-table" cellpadding="10">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Action name</th>
|
||||
<th>Shortcuts</th>
|
||||
<th>Default shortcuts</th>
|
||||
<th>Description</th>
|
||||
<tr class="text-nowrap">
|
||||
<th>${t('shortcuts.action_name')}</th>
|
||||
<th>${t('shortcuts.shortcuts')}</th>
|
||||
<th>${t('shortcuts.default_shortcuts')}</th>
|
||||
<th>${t('shortcuts.description')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
@ -51,9 +52,9 @@ const TPL = `
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-options-buttons">
|
||||
<button class="options-keyboard-shortcuts-reload-app btn btn-primary">Reload app to apply changes</button>
|
||||
<button class="options-keyboard-shortcuts-reload-app btn btn-primary">${t('shortcuts.reload_app')}</button>
|
||||
|
||||
<button class="options-keyboard-shortcuts-set-all-to-default btn">Set all shortcuts to the default</button>
|
||||
<button class="options-keyboard-shortcuts-set-all-to-default btn">${t('shortcuts.set_all_to_default')}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
@ -83,10 +84,10 @@ export default class KeyboardShortcutsOptions extends OptionsWidget {
|
||||
else {
|
||||
$tr.append($("<td>").text(action.actionName))
|
||||
.append($("<td>").append(
|
||||
$(`<input type="text" class="form-control">`)
|
||||
.val(action.effectiveShortcuts.join(", "))
|
||||
.attr('data-keyboard-action-name', action.actionName)
|
||||
.attr('data-default-keyboard-shortcuts', action.defaultShortcuts.join(", "))
|
||||
$(`<input type="text" class="form-control">`)
|
||||
.val(action.effectiveShortcuts.join(", "))
|
||||
.attr('data-keyboard-action-name', action.actionName)
|
||||
.attr('data-default-keyboard-shortcuts', action.defaultShortcuts.join(", "))
|
||||
)
|
||||
)
|
||||
.append($("<td>").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))
|
||||
|
@ -1,35 +1,36 @@
|
||||
import utils from "../../../services/utils.js";
|
||||
import OptionsWidget from "./options_widget.js";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
|
||||
const TPL_WEB = `
|
||||
<div class="options-section">
|
||||
<h4>Spell Check</h4>
|
||||
<h4>${t('spellcheck.title')}</h4>
|
||||
|
||||
<p>These options apply only for desktop builds, browsers will use their own native spell check.</p>
|
||||
<p>${t('spellcheck.description')}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const TPL_ELECTRON = `
|
||||
<div class="options-section">
|
||||
<h4>Spell Check</h4>
|
||||
<h4>${t('spellcheck.title')}</h4>
|
||||
|
||||
<p>App restart is required after change.</p>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" class="spell-check-enabled">
|
||||
Enable spellcheck
|
||||
${t('spellcheck.enable')}
|
||||
</label>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Language code(s)</label>
|
||||
<input type="text" class="spell-check-language-code form-control" placeholder="for example "en-US", "de-AT"">
|
||||
<label>${t('spellcheck.language_code_label')}</label>
|
||||
<input type="text" class="spell-check-language-code form-control" placeholder="${t('spellcheck.language_code_placeholder')}">
|
||||
</div>
|
||||
|
||||
<p>Multiple languages can be separated by comma, e.g. <code>en-US, de-DE, cs</code>. Changes to the spell check options will take effect after application restart.</p>
|
||||
<p>${t('spellcheck.multiple_languages_info')}</p>
|
||||
|
||||
<p><strong>Available language codes: </strong> <span class="available-language-codes"></span></p>
|
||||
<p><strong>${t('spellcheck.available_language_codes_label')} </strong> <span class="available-language-codes"></span></p>
|
||||
</div>`;
|
||||
|
||||
export default class SpellcheckOptions extends OptionsWidget {
|
||||
|
@ -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 = `
|
||||
<div class="options-section">
|
||||
<h4 style="margin-top: 0px;">Sync Configuration</h4>
|
||||
<h4 style="margin-top: 0px;">${t('sync_2.config_title')}</h4>
|
||||
|
||||
<form class="sync-setup-form">
|
||||
<div class="form-group">
|
||||
<label>Server instance address</label>
|
||||
<label>${t('sync_2.server_address')}</label>
|
||||
<input class="sync-server-host form-control" placeholder="https://<host>:<port>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Sync timeout (milliseconds)</label>
|
||||
<label>${t('sync_2.timeout')}</label>
|
||||
<input class="sync-server-timeout form-control" min="1" max="10000000" type="number" style="text-align: left;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Sync proxy server (optional)</label>
|
||||
<label>${t('sync_2.proxy_label')}</label>
|
||||
<input class="sync-proxy form-control" placeholder="https://<host>:<port>">
|
||||
|
||||
<p><strong>Note:</strong> If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only).</p>
|
||||
<p>Another special value is <code>noproxy</code> which forces ignoring even the system proxy and respectes <code>NODE_TLS_REJECT_UNAUTHORIZED</code>.</p>
|
||||
<p><strong>${t('sync_2.note')}:</strong> ${t('sync_2.note_description')}</p>
|
||||
<p>${t('sync_2.special_value_description')}</p>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
<button class="btn btn-primary">${t('sync_2.save')}</button>
|
||||
|
||||
<button class="btn" type="button" data-help-page="synchronization.html">Help</button>
|
||||
<button class="btn" type="button" data-help-page="synchronization.html">${t('sync_2.help')}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="options-section">
|
||||
<h4>Sync Test</h4>
|
||||
<h4>${t('sync_2.test_title')}</h4>
|
||||
|
||||
<p>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.</p>
|
||||
<p>${t('sync_2.test_description')}</p>
|
||||
|
||||
<button class="test-sync-button btn">Test sync</button>
|
||||
<button class="test-sync-button btn">${t('sync_2.test_button')}</button>
|
||||
</div>`;
|
||||
|
||||
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 }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1114,5 +1114,133 @@
|
||||
"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": "尚无备份"
|
||||
},
|
||||
"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 将重新加载。"
|
||||
},
|
||||
"shortcuts": {
|
||||
"keyboard_shortcuts": "快捷键",
|
||||
"multiple_shortcuts": "同一操作的多个快捷键可以用逗号分隔。",
|
||||
"electron_documentation": "请参阅 <a href=\"https://www.electronjs.org/docs/latest/api/accelerator\">Electron文档</a>,了解可用的修饰符和键码。",
|
||||
"type_text_to_filter": "输入文字以过滤快捷键...",
|
||||
"action_name": "操作名称",
|
||||
"shortcuts": "快捷键",
|
||||
"default_shortcuts": "默认快捷键",
|
||||
"description": "描述",
|
||||
"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": "另一个特殊值是 <code>noproxy</code>,它强制忽略系统代理并遵守 <code>NODE_TLS_REJECT_UNAUTHORIZED</code>。",
|
||||
"save": "保存",
|
||||
"help": "帮助",
|
||||
"test_title": "同步测试",
|
||||
"test_description": "测试和同步服务器之间的连接。如果同步服务器没有初始化,会将本地文档同步到同步服务器上。",
|
||||
"test_button": "测试同步",
|
||||
"handshake_failed": "同步服务器握手失败,错误:{{message}}"
|
||||
},
|
||||
"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}}'。"
|
||||
},
|
||||
"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": "无论笔记长度如何,始终可编辑。"
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user