diff --git a/packages/desktop/src/common/i18n.ts b/packages/desktop/src/common/i18n.ts index 561b2da720..49b1980962 100644 --- a/packages/desktop/src/common/i18n.ts +++ b/packages/desktop/src/common/i18n.ts @@ -3,7 +3,7 @@ import path from 'path' export type Translations = Record -const SUPPORTED_LANGUAGES = ['en', 'zh-CN', 'zh-TW', 'es', 'fr', 'de', 'ja', 'ko', 'pt', 'tr'] as const +const SUPPORTED_LANGUAGES = ['en', 'zh-CN', 'zh-TW', 'es', 'fr', 'de', 'ja', 'ko', 'nl', 'pt', 'tr'] as const export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number] diff --git a/packages/desktop/src/main/app/index.ts b/packages/desktop/src/main/app/index.ts index 74411b3f34..9b0fbf50c4 100644 --- a/packages/desktop/src/main/app/index.ts +++ b/packages/desktop/src/main/app/index.ts @@ -165,6 +165,7 @@ class App { 'fr', 'de', 'es', + 'nl', 'pt', 'ru' ] @@ -188,6 +189,9 @@ class App { 'de-DE': 'de', es: 'es', 'es-ES': 'es', + nl: 'nl', + 'nl-NL': 'nl', + 'nl-BE': 'nl', pt: 'pt', 'pt-BR': 'pt', ru: 'ru', diff --git a/packages/desktop/src/renderer/src/prefComponents/general/config.ts b/packages/desktop/src/renderer/src/prefComponents/general/config.ts index c5c329a514..94d33bdd76 100644 --- a/packages/desktop/src/renderer/src/prefComponents/general/config.ts +++ b/packages/desktop/src/renderer/src/prefComponents/general/config.ts @@ -128,6 +128,10 @@ export const getLanguageOptions = (): PrefSelectOption[] => [ label: t('preferences.general.misc.language.korean'), value: 'ko' }, + { + label: t('preferences.general.misc.language.dutch'), + value: 'nl' + }, { label: t('preferences.general.misc.language.portuguese'), value: 'pt' diff --git a/packages/desktop/static/locales/de.json b/packages/desktop/static/locales/de.json index bbd1acb248..9a3b900982 100644 --- a/packages/desktop/static/locales/de.json +++ b/packages/desktop/static/locales/de.json @@ -241,7 +241,9 @@ "openFolder": "Ordner öffnen" }, "toc": { - "title": "Inhaltsverzeichnis" + "title": "Inhaltsverzeichnis", + "expandAll": "Alle ausklappen", + "collapseAll": "Alle einklappen" }, "icons": { "files": "Dateien", @@ -497,6 +499,7 @@ "german": "Deutsch", "japanese": "日本語", "korean": "한국어", + "dutch": "Nederlands", "portuguese": "Português", "turkish": "Türkçe" } diff --git a/packages/desktop/static/locales/en.json b/packages/desktop/static/locales/en.json index e56f616389..e3958df650 100644 --- a/packages/desktop/static/locales/en.json +++ b/packages/desktop/static/locales/en.json @@ -241,7 +241,9 @@ "openFolder": "Open Folder" }, "toc": { - "title": "Table Of Contents" + "title": "Table Of Contents", + "expandAll": "Expand all", + "collapseAll": "Collapse all" }, "icons": { "files": "Files", @@ -497,6 +499,7 @@ "german": "Deutsch", "japanese": "日本語", "korean": "한국어", + "dutch": "Nederlands", "portuguese": "Português", "turkish": "Türkçe" } diff --git a/packages/desktop/static/locales/es.json b/packages/desktop/static/locales/es.json index 9db59943c6..e1a2dd80d8 100644 --- a/packages/desktop/static/locales/es.json +++ b/packages/desktop/static/locales/es.json @@ -73,7 +73,7 @@ "taskList": "Lista de Tareas", "looseListItem": "Elemento de Lista Suelto", "horizontalRule": "Regla Horizontal", - "frontMatter": "Metadatos" + "frontMatter": "Front Matter" }, "window": { "title": "Ventana", @@ -241,7 +241,9 @@ "openFolder": "Abrir Carpeta" }, "toc": { - "title": "Tabla de Contenidos" + "title": "Tabla de Contenidos", + "expandAll": "Expandir todo", + "collapseAll": "Contraer todo" }, "icons": { "files": "Archivos", @@ -497,6 +499,7 @@ "german": "Deutsch", "japanese": "日本語", "korean": "한국어", + "dutch": "Nederlands", "portuguese": "Português", "turkish": "Türkçe" } @@ -801,7 +804,7 @@ "looseListItem": "Elemento de Lista Suelto", "paragraph": "Párrafo", "horizontalLine": "Línea Horizontal", - "frontMatter": "Metadatos", + "frontMatter": "Front Matter", "resetParagraph": "Restablecer Párrafo", "mathBlock": "Bloque matemático", "horizontalRule": "Línea horizontal" diff --git a/packages/desktop/static/locales/fr.json b/packages/desktop/static/locales/fr.json index 216696e3ea..8503e2d864 100644 --- a/packages/desktop/static/locales/fr.json +++ b/packages/desktop/static/locales/fr.json @@ -73,7 +73,7 @@ "taskList": "Liste de tâches", "looseListItem": "Élément de liste lâche", "horizontalRule": "Règle horizontale", - "frontMatter": "Métadonnées" + "frontMatter": "Front Matter" }, "window": { "title": "Fenêtre", @@ -241,7 +241,9 @@ "openFolder": "Ouvrir un dossier" }, "toc": { - "title": "Table des matières" + "title": "Table des matières", + "expandAll": "Tout développer", + "collapseAll": "Tout réduire" }, "icons": { "files": "Fichiers", @@ -497,6 +499,7 @@ "german": "Deutsch", "japanese": "日本語", "korean": "한국어", + "dutch": "Nederlands", "portuguese": "Português", "turkish": "Türkçe" } @@ -579,7 +582,7 @@ }, "uploader": { "title": "Téléchargeur d'images", - "currentUploader": "Téléchargeur actuel", + "currentUploader": "Téléverseur actuel : {name}", "picgoNotInstalled": "PicGo non installé", "picgoInstalled": "PicGo installé", "picgoDetectionFailed": "Échec de la détection PicGo", @@ -699,7 +702,7 @@ "online": "En ligne", "saveError": "Erreur lors de l'enregistrement des raccourcis clavier", "shortcutInUse": "Raccourci en cours d'utilisation", - "shortcutInUseMessage": "Ce raccourci est déjà assigné à une autre commande", + "shortcutInUseMessage": "Ce raccourci est déjà assigné à {accelerator}", "table": { "description": "Description", "edit": "Modifier", @@ -728,7 +731,7 @@ } }, "about": { - "copyright": "Droits d'auteur", + "copyright": "Copyright © Luo Ran 2017-{year}", "copyrightContributors": "Contributeurs aux droits d'auteur" }, "commands": { @@ -801,7 +804,7 @@ "looseListItem": "Élément de liste lâche", "paragraph": "Paragraphe", "horizontalLine": "Ligne horizontale", - "frontMatter": "Métadonnées", + "frontMatter": "Front Matter", "resetParagraph": "Réinitialiser le paragraphe", "mathBlock": "Bloc mathématique", "horizontalRule": "Ligne horizontale" @@ -881,7 +884,7 @@ "close": "Fermer", "dontSave": "Ne pas enregistrer", "file": "Fichier", - "fileExists": "Le fichier existe", + "fileExists": "Le fichier existe déjà : {filename}", "files": "Fichiers", "importWarning": "Avertissement d'importation", "installPandoc": "Installer Pandoc", @@ -898,12 +901,12 @@ "error": { "configSchemaViolation": "Violation du schéma de configuration", "copyError": "Erreur de copie", - "initializationFailed": "Échec de l'initialisation", + "initializationFailed": "Échec de l'initialisation {hint}", "otherInstanceDetected": "Une autre instance de MarkText détectée en cours d'exécution", "report": "Signaler", "startupError": "Erreur de démarrage", "terminatedDueToError": "Terminé en raison d'une erreur", - "unexpectedErrorWithMessage": "Message d'erreur inattendue", + "unexpectedErrorWithMessage": "Erreur inattendue : {message}", "unexpectedMainProcess": "Erreur inattendue du processus principal", "unexpectedRendererProcess": "Erreur inattendue du processus de rendu" }, @@ -915,7 +918,7 @@ "editor": { "anchorLinkCopied": "Lien d'ancrage copié", "tabNotFound": "Onglet introuvable", - "tocItemNotFound": "Élément de table des matières introuvable", + "tocItemNotFound": "Élément de la table des matières {key} introuvable", "errorLoadingTabTitle": "Erreur lors du chargement de l’onglet", "errorLoadingTabMessage": "Une erreur s’est produite lors du chargement de la modification du fichier car l’onglet est introuvable.", "mixedLineEndingsNormalized": "« {name} » contient des fins de ligne mixtes qui ont été automatiquement normalisées en {lineEnding}.", @@ -935,8 +938,8 @@ "editor": { "export": { "error": "Erreur d'exportation", - "errorExporting": "Erreur lors de l'exportation du fichier", - "failed": "Échec de l'exportation" + "errorExporting": "Erreur lors de l'exportation du fichier {type}", + "failed": "Échec de l'exportation {type}" }, "insertTable": { "title": "Insérer un tableau", @@ -947,7 +950,7 @@ "notificationNotFound": "Notification introuvable" }, "print": { - "error": "Erreur d'impression", + "error": "Erreur d'impression pour \"{title}\"", "failed": "Échec de l'impression" }, "sourceCode": { @@ -956,9 +959,9 @@ }, "spellcheck": { "disabledError": "Erreur de désactivation de la vérification orthographique", - "errorSwitchingLanguage": "Erreur lors du changement de langue de vérification orthographique", - "languageMissing": "Langue de vérification orthographique manquante", - "switchError": "Erreur de changement de vérification orthographique", + "errorSwitchingLanguage": "Erreur lors du changement de langue du correcteur {languageCode}", + "languageMissing": "Langue du correcteur manquante {languageCode}", + "switchError": "Erreur de changement du correcteur pour {languageCode}, {error}", "title": "Vérification orthographique" } } diff --git a/packages/desktop/static/locales/ja.json b/packages/desktop/static/locales/ja.json index f48c1ad419..a2ce8680a4 100644 --- a/packages/desktop/static/locales/ja.json +++ b/packages/desktop/static/locales/ja.json @@ -241,7 +241,9 @@ "openFolder": "フォルダを開く" }, "toc": { - "title": "目次" + "title": "目次", + "expandAll": "すべて展開", + "collapseAll": "すべて折りたたむ" }, "icons": { "files": "ファイル", @@ -497,6 +499,7 @@ "german": "Deutsch", "japanese": "日本語", "korean": "한국어", + "dutch": "Nederlands", "portuguese": "Português", "turkish": "Türkçe" } @@ -579,7 +582,7 @@ }, "uploader": { "title": "画像アップローダー", - "currentUploader": "現在のアップローダー", + "currentUploader": "現在のアップローダー: {name}", "picgoNotInstalled": "PicGoがインストールされていません", "picgoInstalled": "PicGoがインストールされています", "picgoDetectionFailed": "PicGo検出に失敗しました", @@ -699,7 +702,7 @@ "online": "オンライン", "saveError": "キーバインドの保存エラー", "shortcutInUse": "ショートカットが使用中", - "shortcutInUseMessage": "このショートカットは既に別のコマンドに割り当てられています", + "shortcutInUseMessage": "このショートカットは既に {accelerator} に割り当てられています", "table": { "description": "説明", "edit": "編集", @@ -728,7 +731,7 @@ } }, "about": { - "copyright": "著作権", + "copyright": "Copyright © Luo Ran 2017-{year}", "copyrightContributors": "著作権貢献者" }, "commands": { @@ -881,7 +884,7 @@ "close": "閉じる", "dontSave": "保存しない", "file": "ファイル", - "fileExists": "ファイルが存在します", + "fileExists": "ファイルが既に存在します: {filename}", "files": "ファイル", "importWarning": "インポート警告", "installPandoc": "Pandocをインストール", @@ -898,12 +901,12 @@ "error": { "configSchemaViolation": "設定スキーマ違反", "copyError": "コピーエラー", - "initializationFailed": "初期化に失敗", + "initializationFailed": "初期化に失敗しました {hint}", "otherInstanceDetected": "MarkTextの別のインスタンスが既に実行中です", "report": "報告", "startupError": "起動エラー", "terminatedDueToError": "エラーにより終了", - "unexpectedErrorWithMessage": "予期しないエラーメッセージ", + "unexpectedErrorWithMessage": "予期しないエラー: {message}", "unexpectedMainProcess": "予期しないメインプロセスエラー", "unexpectedRendererProcess": "予期しないレンダラープロセスエラー" }, @@ -915,7 +918,7 @@ "editor": { "anchorLinkCopied": "アンカーリンクがコピーされました", "tabNotFound": "タブが見つかりません", - "tocItemNotFound": "目次アイテムが見つかりません", + "tocItemNotFound": "目次項目 {key} が見つかりません", "errorLoadingTabTitle": "タブの読み込みエラー", "errorLoadingTabMessage": "ファイル変更の読み込み中にエラーが発生しました。タブが見つかりません。", "mixedLineEndingsNormalized": "「{name}」には混在した改行コードが含まれており、{lineEnding} に自動正規化されました。", @@ -935,8 +938,8 @@ "editor": { "export": { "error": "エクスポートエラー", - "errorExporting": "ファイルのエクスポート中にエラーが発生しました", - "failed": "エクスポートに失敗しました" + "errorExporting": "{type} ファイルのエクスポートエラー", + "failed": "{type} エクスポートに失敗しました" }, "insertTable": { "title": "表を挿入", @@ -947,7 +950,7 @@ "notificationNotFound": "通知が見つかりません" }, "print": { - "error": "印刷エラー", + "error": "\"{title}\" の印刷エラー", "failed": "印刷に失敗しました" }, "sourceCode": { @@ -956,9 +959,9 @@ }, "spellcheck": { "disabledError": "スペルチェックの無効化エラー", - "errorSwitchingLanguage": "スペルチェック言語の切り替えエラー", - "languageMissing": "スペルチェック言語が見つかりません", - "switchError": "スペルチェックの切り替えエラー", + "errorSwitchingLanguage": "スペルチェック言語の切り替えエラー {languageCode}", + "languageMissing": "スペルチェック言語が見つかりません {languageCode}", + "switchError": "{languageCode} のスペルチェック切り替えエラー、{error}", "title": "スペルチェック" } } diff --git a/packages/desktop/static/locales/ko.json b/packages/desktop/static/locales/ko.json index 84a9ea2bba..07a7652091 100644 --- a/packages/desktop/static/locales/ko.json +++ b/packages/desktop/static/locales/ko.json @@ -241,7 +241,9 @@ "openFolder": "폴더 열기" }, "toc": { - "title": "목차" + "title": "목차", + "expandAll": "모두 펼치기", + "collapseAll": "모두 접기" }, "icons": { "files": "파일", @@ -497,6 +499,7 @@ "german": "Deutsch", "japanese": "日本語", "korean": "한국어", + "dutch": "Nederlands", "portuguese": "Português", "turkish": "Türkçe" } @@ -579,7 +582,7 @@ }, "uploader": { "title": "이미지 업로더", - "currentUploader": "현재 업로더", + "currentUploader": "현재 업로더: {name}", "picgoNotInstalled": "PicGo가 설치되지 않음", "picgoInstalled": "PicGo가 설치됨", "picgoDetectionFailed": "PicGo 감지 실패", @@ -699,7 +702,7 @@ "online": "온라인", "saveError": "키 바인딩 저장 오류", "shortcutInUse": "사용 중인 단축키", - "shortcutInUseMessage": "이 단축키는 이미 다른 명령에 할당되어 있습니다", + "shortcutInUseMessage": "이 단축키는 이미 {accelerator}에 할당되어 있습니다", "table": { "description": "설명", "edit": "편집", @@ -728,7 +731,7 @@ } }, "about": { - "copyright": "저작권", + "copyright": "Copyright © Luo Ran 2017-{year}", "copyrightContributors": "저작권 기여자" }, "commands": { @@ -881,7 +884,7 @@ "close": "닫기", "dontSave": "저장하지 않음", "file": "파일", - "fileExists": "파일이 존재합니다", + "fileExists": "파일이 이미 존재합니다: {filename}", "files": "파일", "importWarning": "가져오기 경고", "installPandoc": "Pandoc 설치", @@ -898,12 +901,12 @@ "error": { "configSchemaViolation": "구성 스키마 위반", "copyError": "복사 오류", - "initializationFailed": "초기화 실패", + "initializationFailed": "초기화 실패 {hint}", "otherInstanceDetected": "MarkText의 다른 인스턴스가 이미 실행 중입니다", "report": "신고", "startupError": "시작 오류", "terminatedDueToError": "오류로 인해 종료됨", - "unexpectedErrorWithMessage": "메시지와 함께 예상치 못한 오류", + "unexpectedErrorWithMessage": "예기치 않은 오류: {message}", "unexpectedMainProcess": "예상치 못한 메인 프로세스 오류", "unexpectedRendererProcess": "예상치 못한 렌더러 프로세스 오류" }, @@ -915,7 +918,7 @@ "editor": { "anchorLinkCopied": "앵커 링크 복사됨", "tabNotFound": "탭을 찾을 수 없음", - "tocItemNotFound": "목차 항목을 찾을 수 없음", + "tocItemNotFound": "목차 항목 {key}을(를) 찾을 수 없습니다", "errorLoadingTabTitle": "탭 로드 오류", "errorLoadingTabMessage": "파일 변경을 로드하는 동안 오류가 발생했습니다. 탭을 찾을 수 없습니다.", "mixedLineEndingsNormalized": "“{name}”에 혼합 줄 끝이 포함되어 있으며 {lineEnding}(으)로 자동 정규화되었습니다.", @@ -935,8 +938,8 @@ "editor": { "export": { "error": "내보내기 오류", - "errorExporting": "파일 내보내기 오류", - "failed": "내보내기 실패" + "errorExporting": "{type} 파일 내보내기 오류", + "failed": "{type} 내보내기 실패" }, "insertTable": { "title": "표 삽입", @@ -947,7 +950,7 @@ "notificationNotFound": "알림을 찾을 수 없음" }, "print": { - "error": "인쇄 오류", + "error": "\"{title}\" 인쇄 오류", "failed": "인쇄 실패" }, "sourceCode": { @@ -956,9 +959,9 @@ }, "spellcheck": { "disabledError": "맞춤법 검사 비활성화 오류", - "errorSwitchingLanguage": "맞춤법 검사 언어 전환 오류", - "languageMissing": "맞춤법 검사 언어가 없습니다", - "switchError": "맞춤법 검사 전환 오류", + "errorSwitchingLanguage": "맞춤법 검사 언어 전환 오류 {languageCode}", + "languageMissing": "맞춤법 검사 언어 누락 {languageCode}", + "switchError": "{languageCode} 맞춤법 검사 전환 오류, {error}", "title": "맞춤법 검사" } } diff --git a/packages/desktop/static/locales/nl.json b/packages/desktop/static/locales/nl.json new file mode 100644 index 0000000000..e7b8097ca0 --- /dev/null +++ b/packages/desktop/static/locales/nl.json @@ -0,0 +1,968 @@ +{ + "menu": { + "counter": { + "words": "Woorden", + "characters": "Tekens", + "paragraphs": "Alinea's" + }, + "file": { + "file": "Bestand", + "newTab": "Nieuw tabblad", + "newWindow": "Nieuw venster", + "openFile": "Bestand openen...", + "openFolder": "Map openen...", + "openRecent": "Recent geopend", + "clearRecentlyUsed": "Recent gewist", + "save": "Opslaan", + "saveAs": "Opslaan als...", + "autoSave": "Automatisch opslaan", + "moveTo": "Verplaatsen naar", + "rename": "Hernoemen", + "export": "Exporteren", + "exportHtml": "Exporteren als HTML", + "exportPdf": "Exporteren als PDF", + "import": "Importeren", + "print": "Afdrukken", + "closeTab": "Tabblad sluiten", + "closeWindow": "Venster sluiten", + "quit": "Afsluiten", + "preferences": "Voorkeuren" + }, + "edit": { + "edit": "Bewerken", + "undo": "Ongedaan maken", + "redo": "Opnieuw", + "cut": "Knippen", + "copy": "Kopiëren", + "paste": "Plakken", + "copyAsRich": "Kopiëren als opgemaakte tekst", + "copyAsHtml": "Kopiëren als HTML", + "pasteAsPlainText": "Plakken als platte tekst", + "selectAll": "Alles selecteren", + "duplicate": "Dupliceren", + "createParagraph": "Alinea aanmaken", + "deleteParagraph": "Alinea verwijderen", + "find": "Zoeken", + "replace": "Vervangen", + "findNext": "Volgende zoeken", + "findPrevious": "Vorige zoeken", + "findInFolder": "Zoeken in map", + "screenshot": "Schermafbeelding", + "lineEnding": "Regeleinde", + "lineEndingCrlf": "CRLF (Windows)", + "lineEndingLf": "LF (Linux/Mac)" + }, + "paragraph": { + "title": "Alinea", + "paragraph": "Alinea", + "heading1": "Kop 1", + "heading2": "Kop 2", + "heading3": "Kop 3", + "heading4": "Kop 4", + "heading5": "Kop 5", + "heading6": "Kop 6", + "promoteHeading": "Kop promoveren", + "demoteHeading": "Kop degraderen", + "table": "Tabel", + "codeFences": "Codeblok", + "quoteBlock": "Citaatblok", + "mathBlock": "Wiskundeblok", + "htmlBlock": "HTML-blok", + "orderedList": "Genummerde lijst", + "bulletList": "Opsommingslijst", + "taskList": "Takenlijst", + "looseListItem": "Los lijstitem", + "horizontalRule": "Horizontale lijn", + "frontMatter": "Front Matter" + }, + "window": { + "title": "Venster", + "minimize": "Minimaliseren", + "zoomIn": "Inzoomen", + "zoomOut": "Uitzoomen", + "fullScreen": "Volledig scherm", + "bringAllToFront": "Alles naar voren", + "alwaysOnTop": "Altijd bovenaan" + }, + "help": { + "help": "Help", + "markdownReference": "Markdown-referentie", + "changelog": "Wijzigingslog", + "askQuestion": "Vraag stellen", + "reportBug": "Bug melden", + "viewSource": "Broncode bekijken", + "followUs": "Volg ons", + "support": "Steun MarkText", + "license": "Licentie", + "about": "Over", + "checkUpdates": "Controleer op updates" + }, + "marktext": { + "title": "MarkText", + "about": "Over MarkText", + "checkUpdates": "Controleer op updates...", + "preferences": "Voorkeuren", + "services": "Diensten", + "hide": "Verberg MarkText", + "hideOthers": "Verberg overige", + "showAll": "Toon alles", + "quit": "MarkText afsluiten" + }, + "view": { + "view": "Weergave", + "toggleSidebar": "Zijbalk in-/uitschakelen", + "toggleTabbar": "Tabbalk in-/uitschakelen", + "commandPalette": "Opdrachtpalet", + "sourceCodeMode": "Broncodeweergave", + "typewriterMode": "Typemachinestand", + "focusMode": "Focusstand", + "toggleTableOfContents": "Inhoudsopgave in-/uitschakelen", + "reloadImages": "Afbeeldingen herladen", + "showDeveloperTools": "Ontwikkelaarshulpmiddelen tonen", + "reloadWindow": "Venster herladen" + }, + "format": { + "format": "Opmaak", + "bold": "Vet", + "italic": "Cursief", + "underline": "Onderstreept", + "strikethrough": "Doorgestreept", + "image": "Afbeelding", + "superscript": "Superscript", + "subscript": "Subscript", + "highlight": "Markering", + "inlineCode": "Inline code", + "inlineMath": "Inline wiskunde", + "hyperlink": "Hyperlink", + "clearFormat": "Opmaak wissen" + }, + "theme": { + "theme": "&Thema", + "dark": "Donker", + "followThemDisabled": "Themaselectie uitgeschakeld bij het volgen van systeemthema", + "lightThemes": "— Lichte thema's —", + "darkThemes": "— Donkere thema's —", + "ayuDark": "Ayu Dark", + "ayuLight": "Ayu Light", + "ayuMirage": "Ayu Mirage", + "cadmiumDark": "Cadmium Dark", + "cadmiumLight": "Cadmium Light", + "catppuccinLatte": "Catppuccin Latte", + "catppuccinMocha": "Catppuccin Mocha", + "cyberdream": "cyberdream", + "dracula": "Dracula", + "everforestDark": "Everforest Dark", + "everforestLight": "Everforest Light", + "graphiteLight": "Graphite Light", + "gruvboxDark": "Gruvbox Dark", + "gruvboxLight": "Gruvbox Light", + "horizonDark": "Horizon Dark", + "kanagawa": "Kanagawa", + "materialDark": "Material Dark", + "monokaiPro": "Monokai Pro", + "nightfox": "Nightfox", + "nord": "Nord", + "oneDark": "One Dark", + "oxocarbonDark": "Oxocarbon Dark", + "palenight": "Palenight", + "rosePine": "Rosé Pine", + "rosePineDawn": "Rosé Pine Dawn", + "rosePineMoon": "Rosé Pine Moon", + "solarizedDark": "Solarized Dark", + "solarizedLight": "Solarized Light", + "synthwave84": "Synthwave '84", + "tokyoNight": "Tokyo Night", + "tokyoNightLight": "Tokyo Night Light", + "tokyoNightStorm": "Tokyo Night Storm", + "ulyssesLight": "Ulysses Light" + } + }, + "contextMenu": { + "cut": "Knippen", + "copy": "Kopiëren", + "paste": "Plakken", + "copyAsRich": "Kopiëren als opgemaakte tekst", + "copyAsHtml": "Kopiëren als HTML", + "pasteAsPlainText": "Plakken als platte tekst", + "insertParagraphBefore": "Alinea invoegen vóór", + "insertParagraphAfter": "Alinea invoegen na", + "spelling": "Spelling...", + "changeLanguage": "Taal wijzigen...", + "addToDictionary": "Toevoegen aan woordenboek", + "editDictionary": "Woordenboek bewerken...", + "sideBar": { + "newFile": "Nieuw bestand", + "newDirectory": "Nieuwe map", + "copy": "Kopiëren", + "cut": "Knippen", + "paste": "Plakken", + "rename": "Hernoemen", + "moveToTrash": "Naar prullenbak", + "showInFolder": "Tonen in map" + }, + "tabs": { + "close": "Sluiten", + "closeOthers": "Andere sluiten", + "closeSavedTabs": "Opgeslagen tabbladen sluiten", + "closeAllTabs": "Alle tabbladen sluiten", + "rename": "Hernoemen", + "copyPath": "Pad kopiëren", + "showInFolder": "Tonen in map" + } + }, + "search": { + "searchPlaceholder": "Zoeken", + "caseSensitive": "Hoofdlettergevoelig", + "wholeWord": "Heel woord selecteren", + "useRegex": "Gebruik als reguliere expressie", + "replacementPlaceholder": "Vervanging", + "replaceAll": "Alles vervangen", + "replaceSingle": "Enkele vervanging", + "invalidRegex": "Ongeldige reguliere expressie \"{pattern}\"", + "regexMatchEmpty": "Reguliere expressie matcht lege tekenreeks \"{pattern}\"", + "searchResultInfo": "{matchCount} resultaten in {fileCount} bestanden", + "searchLimited": "Zoeken beperkt tot eerste {count} resultaten" + }, + "sideBar": { + "search": { + "searchInFolder": "Zoeken in map...", + "noFolderOpen": "Geen map geopend", + "noResultsFound": "Geen resultaten gevonden.", + "cancel": "Annuleren", + "openFolder": "Map openen", + "showMoreMatches": "Meer resultaten tonen" + }, + "tree": { + "openedFiles": "Geopende bestanden", + "saveAll": "Alles opslaan", + "closeAll": "Alles sluiten", + "emptyProject": "Leeg project", + "createFile": "Bestand aanmaken", + "openFolder": "Map openen" + }, + "toc": { + "title": "Inhoudsopgave", + "expandAll": "Alles uitklappen", + "collapseAll": "Alles inklappen" + }, + "icons": { + "files": "Bestanden", + "search": "Zoeken", + "toc": "Inhoudsopgave", + "settings": "Instellingen" + } + }, + "commandPalette": { + "placeholder": "Typ een opdracht om uit te voeren", + "placeholders": { + "selectOption": "Selecteer een optie", + "searchFileToOpen": "Zoek bestand om te openen", + "selectLanguage": "Selecteer een taal om naar te wisselen" + } + }, + "exportSettings": { + "autoNumberingHeadings": "Automatisch nummeren van koppen", + "title": "Exportopties", + "info": { + "label": "Info", + "description": "Pas de pagina-opmaak aan en klik op \"exporteren\" om door te gaan." + }, + "page": { + "label": "Pagina", + "pageTitle": "Paginatitel:", + "pageSize": "Paginaformaat:", + "landscapeOrientation": "Liggende oriëntatie:", + "pageMargin": "Paginamarge in mm:", + "topBottom": "Boven/Onder:", + "leftRight": "Links/Rechts:", + "widthHeight": "Breedte/Hoogte in mm:" + }, + "showFrontMatter": "Front Matter tonen", + "style": { + "label": "Stijl", + "overwriteThemeFont": "Thema-lettertype overschrijven", + "fontFamily": "Lettertypefamilie:", + "fontSize": "Lettergrootte", + "lineHeight": "Regelhoogte" + }, + "theme": { + "label": "Thema", + "description": "Je kunt het uiterlijk van het document aanpassen door een thema te kiezen of een eigen thema te maken.", + "theme": "Thema" + }, + "headerFooter": { + "allowStyled": "Opgemaakte tekst toestaan", + "customizeStyle": "Stijl aanpassen", + "fontSize": "Lettergrootte", + "footerType": "Voetteksttype", + "headerType": "Kopteksttype", + "label": "Kop- & voettekst", + "leftFooterText": "Linker voettekst", + "leftHeaderText": "Linker koptekst", + "mainFooterText": "Hoofdvoettekst", + "mainHeaderText": "Hoofdkoptekst", + "rightFooterText": "Rechter voettekst", + "rightHeaderText": "Rechter koptekst", + "description": "De tekst verschijnt op alle pagina's als kop- en/of voettekst is gedefinieerd." + }, + "toc": { + "includeTopHeading": "Bovenste kop opnemen", + "includeTopHeadingDetail": "Bovenste kop detail opnemen", + "label": "Inhoudsopgave", + "title": "Inhoudsopgave" + }, + "export": "Exporteren...", + "options": { + "pageSizes": { + "a3": "A3 (297mm x 420mm)", + "a4": "A4 (210mm x 297mm)", + "a5": "A5 (148mm x 210mm)", + "legal": "US Legal (8,5\" x 13\")", + "letter": "US Letter (8,5\" x 11\")", + "tabloid": "Tabloid (17\" x 11\")", + "custom": "Aangepast" + }, + "headerFooterTypes": { + "none": "Geen", + "singleCell": "Enkele cel", + "threeCells": "Drie cellen" + }, + "headerFooterStyles": { + "default": "Standaard", + "simple": "Eenvoudig", + "styled": "Opgemaakt" + }, + "themes": { + "academic": "Academisch", + "default": "GitHub (Standaard)", + "liber": "Liber" + } + } + }, + "recent": { + "noTabsOpen": "Je hebt geen tabbladen open.", + "newFile": "Nieuw bestand" + }, + "import": { + "title": "Importeren of openen", + "description": "Sleep hier om je bestanden in MarkText te openen" + }, + "preferences": { + "title": "Voorkeuren", + "search": { + "placeholder": "Voorkeuren zoeken...", + "categories": { + "general": "Algemeen", + "editor": "Editor", + "markdown": "Markdown", + "theme": "Thema", + "image": "Afbeelding", + "view": "Weergave", + "searcher": "Zoeker", + "watcher": "Watcher", + "spelling": "Spelling", + "custom css": "Aangepaste CSS" + }, + "items": { + "autoSave": "Automatisch de bewerkte inhoud opslaan", + "autoSaveDelay": "De tijd in ms na een wijziging voordat het bestand wordt opgeslagen", + "titleBarStyle": "De titelbalkstijl (alleen Windows en Linux)", + "openFilesInNewWindow": "Bestanden openen in een nieuw venster", + "openFolderInNewWindow": "Map openen via menu in een nieuw venster", + "zoom": "Het zoomniveau. Tussen 0,5 en 2,0", + "hideScrollbar": "Schuifbalken verbergen", + "wordWrapInToc": "Tekstterugloop in inhoudsopgave", + "fileSortBy": "Bestanden sorteren op aanmaaktijd, wijzigingstijd of titel", + "startUpAction": "De actie na het opstarten van MarkText", + "restoreLayoutState": "Vorige editorstatus herstellen bij opstarten", + "defaultDirectoryToOpen": "De standaardmap die geopend wordt bij opstarten", + "language": "De taal die MarkText gebruikt", + "editorFontFamily": "Lettertype van de editor", + "fontSize": "Lettergrootte in pixels", + "lineHeight": "Regelhoogte", + "wrapCodeBlocks": "Tekst terugloop in codeblokken", + "editorLineWidth": "Maximale breedte van het editorgebied", + "codeFontSize": "Lettergrootte in codeblokken (12-18)", + "codeFontFamily": "Lettertype voor codeblokken", + "codeBlockLineNumbers": "Regelnummers tonen", + "trimUnnecessaryCodeBlockEmptyLines": "Lege regels aan begin en einde van codeblokken verwijderen", + "autoPairBracket": "Automatisch haakjes aanvullen bij bewerken", + "autoPairMarkdownSyntax": "Markdown-syntax automatisch aanvullen", + "autoPairQuote": "Automatisch aanhalingstekens aanvullen", + "endOfLine": "Het regeleindeteken aan het einde van elke regel", + "defaultEncoding": "De standaard bestandscodering", + "autoGuessEncoding": "Probeer automatisch de bestandscodering te detecteren", + "trimTrailingNewline": "Optie voor afsluitende lege regels", + "textDirection": "De schrijfrichting van de tekst", + "hideQuickInsertHint": "Hint voor snel invoegen verbergen", + "hideLinkPopup": "Link-popup verbergen als cursor op link staat", + "autoCheck": "Automatisch gerelateerde taak aanvinken", + "preferLooseListItem": "Het voorkeurs lijsttype", + "bulletListMarker": "Het teken voor opsommingslijsten", + "orderListDelimiter": "Het scheidingsteken voor genummerde lijsten", + "preferHeadingStyle": "De voorkeursstijl voor koppen", + "tabSize": "Tab vervangen door x spaties", + "listIndentation": "Inspringkeuze voor lijsten", + "frontmatterType": "Het type Front Matter", + "superSubScript": "Pandoc superscript en subscript inschakelen", + "footnote": "Pandoc voetnoten inschakelen", + "isHtmlEnabled": "HTML-weergave inschakelen", + "isGitlabCompatibilityEnabled": "GitLab-compatibiliteitsmodus inschakelen", + "sequenceTheme": "Sequentiediagram thema", + "plantumlServer": "PlantUML server-URL", + "theme": "Selecteer het thema voor MarkText", + "followSystemTheme": "Systeemthema volgen", + "lightModeTheme": "Thema voor lichte modus", + "darkModeTheme": "Thema voor donkere modus", + "spellcheckerEnabled": "Spellingcontrole inschakelen", + "spellcheckerNoUnderline": "Spelfouten niet onderstrepen", + "spellcheckerLanguage": "Taal voor spellingcontrole", + "imageInsertAction": "Standaardgedrag na invoegen van afbeelding", + "imagePreferRelativeDirectory": "Relatieve afbeeldingsmap prefereren", + "imageRelativeDirectoryBase": "Waar relatieve afbeeldingen worden gekopieerd", + "imageRelativeDirectoryName": "Naam van de relatieve afbeeldingsmap", + "autoNormalizeLineEndings": "Regeleindes automatisch normaliseren bij het openen van bestanden. Indien uitgeschakeld worden bestanden ongewijzigd geopend.", + "fileSortOrder": "Sorteervolgorde voor bestanden in geopende mappen: oplopend of aflopend.", + "openedFilesInSidebar": "Geopende bestanden tonen in de zijbalk." + } + }, + "categories": { + "general": "Algemeen", + "editor": "Editor", + "markdown": "Markdown", + "spelling": "Spelling", + "theme": "Thema", + "image": "Afbeelding", + "keybindings": "Sneltoetsen" + }, + "general": { + "title": "Algemeen", + "autoSave": { + "title": "Automatisch opslaan", + "description": "Automatisch opslaan", + "delayDescription": "Vertraging automatisch opslaan" + }, + "window": { + "title": "Venster", + "titleBarStyle": { + "title": "Titelbalkstijl", + "custom": "Aangepast", + "native": "Systeem" + }, + "requiresRestart": "Herstart vereist", + "hideScrollbars": "Schuifbalken verbergen", + "openFilesInNewWindow": "Bestanden in nieuw venster openen", + "openFoldersInNewWindow": "Mappen in nieuw venster openen", + "zoom": "Zoom" + }, + "startup": { + "title": "Opstartopties", + "layoutOptions": "Lay-outopties", + "startupFilesFolders": "Opstartbestanden/-mappen", + "restorePreviousState": "Vorige editorstatus herstellen", + "openBlankState": "Lege status openen", + "openDefaultDirectory": "Standaardmap openen", + "openLastFolder": "Geopende map herstellen", + "restoreAll": "Alle geopende bestanden en mappen herstellen", + "selectFolder": "Map selecteren", + "openBlankPage": "Lege pagina openen" + }, + "sidebar": { + "title": "Zijbalk", + "wrapTextInToc": "Tekstterugloop in inhoudsopgave", + "showOpenedFiles": "Geopende bestanden tonen", + "excludePatterns": "Uitsluitpatronen", + "excludePatternsNotes": "Uitsluitpatronen (opmerkingen)", + "fileSortBy": { + "title": "Bestanden sorteren op", + "creationTime": "Aanmaaktijd", + "modificationTime": "Wijzigingstijd", + "filename": "Bestandsnaam" + }, + "fileSortOrder": { + "title": "Sorteervolgorde", + "aToZ": "A → Z", + "zToA": "Z → A", + "oldestFirst": "Oudste eerst", + "newestFirst": "Nieuwste eerst" + } + }, + "misc": { + "title": "Diversen", + "language": { + "title": "Taal", + "english": "English", + "chinese": "简体中文", + "traditionalChinese": "繁體中文", + "spanish": "Español", + "french": "Français", + "german": "Deutsch", + "japanese": "日本語", + "korean": "한국어", + "dutch": "Nederlands", + "portuguese": "Português", + "turkish": "Türkçe" + } + } + }, + "editor": { + "title": "Editor", + "textEditor": { + "title": "Teksteditor", + "fontSize": "Lettergrootte", + "lineHeight": "Regelhoogte", + "fontFamily": "Lettertypefamilie", + "maxWidth": "Maximale breedte", + "maxWidthNotes": "Maximale breedte van editorinhoud" + }, + "codeBlock": { + "title": "Codeblok", + "fontSize": "Lettergrootte", + "fontFamily": "Lettertypefamilie", + "showLineNumbers": "Regelnummers tonen", + "removeEmptyLines": "Lege regels verwijderen" + }, + "writingBehavior": { + "title": "Schrijfgedrag", + "autoCloseBrackets": "Haakjes automatisch sluiten", + "autoCompleteMarkdown": "Markdown automatisch aanvullen", + "autoCloseQuotes": "Aanhalingstekens automatisch sluiten" + }, + "fileRepresentation": { + "title": "Bestandsweergave", + "tabWidth": "Tabbreedte", + "lineSeparator": "Regelscheidingsteken", + "defaultEncoding": "Standaardcodering", + "autoDetectEncoding": "Codering automatisch detecteren", + "trailingNewlines": { + "title": "Afsluitende lege regels", + "doNothing": "Niets doen", + "ensureOne": "Eén garanderen", + "preserve": "Behouden", + "trimAll": "Alles verwijderen" + }, + "endOfLine": { + "default": "Standaard", + "crlf": "CRLF (Windows)", + "lf": "LF (Linux/Mac)" + } + }, + "misc": { + "title": "Diversen", + "textDirection": { + "title": "Tekstrichting", + "ltr": "Links naar rechts", + "rtl": "Rechts naar links" + }, + "hideQuickInsertHint": "Hint snel invoegen verbergen", + "hideLinkPopup": "Link-popup verbergen", + "autoCheck": "Automatisch aanvinken", + "autoNormalizeLineEndings": "Regeleindes automatisch normaliseren naar LF bij openen", + "wrapCodeBlocks": "Codeblokken terugloopbaar" + } + }, + "image": { + "title": "Afbeelding", + "actions": { + "upload": "Uploaden naar cloud", + "folder": "Kopiëren naar map", + "path": "Absoluut pad gebruiken" + }, + "folderSetting": { + "title": "Mapinstelling", + "globalFolder": "Globale afbeeldingsmap", + "open": "Openen", + "showInFolder": "Tonen in map", + "preferRelative": "Relatieve map prefereren", + "relativeCopyLocation": "Afbeelding kopiëren relatief aan", + "copyRelativeToFile": "Relatief aan bestand", + "copyRelativeToFolder": "Relatief aan map", + "relativeFolderName": "Relatieve mapnaam", + "filenameNote": "De bestandsnaam wordt automatisch gegenereerd." + }, + "uploader": { + "title": "Afbeeldings-uploader", + "currentUploader": "Huidige uploader: {name}", + "picgoNotInstalled": "PicGo is niet geïnstalleerd", + "picgoInstalled": "PicGo is geïnstalleerd", + "picgoDetectionFailed": "PicGo-detectie mislukt", + "debugInfo": "Foutopsporingsinformatie", + "npmInstallCommand": "npm install picgo -g", + "yarnInstallCommand": "yarn global add picgo", + "pnpmInstallCommand": "pnpm add -g picgo", + "chooseInstallMethod": "Kies installatiemethode:", + "retestPicgo": "Opnieuw testen", + "lastDetectionTime": "Laatste detectietijd", + "picgoDetection": "PicGo-detectie", + "usageGuide": { + "title": "PicGo basisgebruikshandleiding", + "step1": "Uploader configureren", + "step1Description": "Gebruik de volgende opdracht om PicGo-uploader te configureren:", + "step2": "Afbeeldingen uploaden", + "step2Description": "Gebruik de volgende opdracht om afbeeldingen te uploaden:", + "step3": "Configuratie bekijken", + "step3Description": "Gebruik de volgende opdracht om de huidige configuratie te bekijken:", + "documentation": "Volledige documentatie bekijken" + }, + "lastSuccessTime": "Laatste succestijd", + "neverDetected": "Nooit gedetecteerd", + "neverSuccessful": "Nooit succesvol", + "scriptDescription": "Scriptbeschrijving", + "scriptLocation": "Scriptlocatie", + "save": "Opslaan", + "saveConfig": "Configuratie opslaan", + "scriptConfigSaved": "Scriptconfiguratie opgeslagen", + "services": { + "picgo": "PicGo", + "cliScript": "CLI-script" + }, + "pleaseInstall": "Installeer a.u.b.", + "scriptPath": "Scriptpad" + }, + "defaultBehavior": "Het standaardgedrag na plakken of slepen van een afbeelding naar MarkText" + }, + "markdown": { + "title": "Markdown", + "extensions": { + "title": "Extensies", + "superSubScript": "Super-/subscript", + "footnote": "Voetnoot", + "footnoteNotes": "Voetnoten", + "frontmatterType": { + "jsonBrace": "JSON met accolades", + "jsonSemicolon": "JSON met puntkomma", + "title": "Front Matter-type" + } + }, + "diagrams": { + "title": "Diagrammen", + "sequenceTheme": { + "title": "Sequentiethema", + "handDrawn": "Handgetekend", + "simple": "Eenvoudig" + }, + "plantumlServer": { + "title": "PlantUML server" + } + }, + "misc": { + "preferHeadingStyle": { + "title": "Voorkeursstijl koppen", + "atx": "ATX-stijl", + "setext": "Setext-stijl" + }, + "title": "Diversen" + }, + "compatibility": { + "enableGitlab": "GitLab-compatibiliteit inschakelen", + "enableHtml": "HTML-ondersteuning inschakelen", + "title": "Compatibiliteit" + }, + "lists": { + "bulletListMarker": "Opsommingsteken", + "listIndentation": { + "title": "Lijstinspringing", + "dfm": "DFM-stijl", + "fourSpaces": "Vier spaties", + "oneSpace": "Eén spatie", + "tab": "Tab", + "threeSpaces": "Drie spaties", + "twoSpaces": "Twee spaties" + }, + "orderListDelimiter": "Scheidingsteken genummerde lijst", + "preferLooseListItem": "Losse lijstitems prefereren", + "title": "Lijsten" + } + }, + "theme": { + "title": "Thema", + "followSystemTheme": "Systeemthema volgen", + "modeThemes": "Themaselectie", + "lightModeTheme": "Thema lichte modus", + "darkModeTheme": "Thema donkere modus", + "customCss": "Aangepaste CSS", + "importCustomThemes": "Aangepaste thema's importeren", + "importTheme": "Thema importeren", + "openFolder": "Map openen", + "openThemesFolder": "Themamap openen" + }, + "keybindings": { + "title": "Sneltoetsen", + "description": "Sneltoetsen aanpassen", + "save": "Opslaan", + "restoreDefaults": "Standaardwaarden herstellen", + "debugOptions": "Foutopsporingsopties", + "dumpKeyboardInfo": "Toetsenbordinformatie exporteren", + "failedToSave": "Opslaan van sneltoetsen mislukt", + "keyInputDialog": { + "instructions": "Druk de gewenste toetscombinatie in", + "invalidKeybinding": "Ongeldige toetscombinatie", + "placeholder": "Druk toetsen in..." + }, + "online": "Online", + "saveError": "Fout bij opslaan sneltoetsen", + "shortcutInUse": "Sneltoets in gebruik", + "shortcutInUseMessage": "Deze sneltoets is al toegewezen aan {accelerator}", + "table": { + "description": "Beschrijving", + "edit": "Bewerken", + "keyCombination": "Toetscombinatie", + "options": "Opties", + "reset": "Herstellen", + "unbind": "Loskoppelen" + } + }, + "selectFont": "Lettertype selecteren", + "spellchecker": { + "autoDetectDescription": "Documenttaal automatisch detecteren", + "autoDetectLanguage": "Taal automatisch detecteren", + "customDictionary": { + "delete": "Verwijderen", + "description": "Voeg aangepaste woorden toe aan je persoonlijk woordenboek", + "noWordsAvailable": "Geen woorden beschikbaar", + "options": "Opties", + "title": "Aangepast woordenboek", + "word": "Woord" + }, + "defaultLanguage": "Standaardtaal", + "enableSpellChecking": "Spellingcontrole inschakelen", + "hideMarksForErrors": "Markering voor spelfouten verbergen", + "title": "Spellingcontrole" + } + }, + "about": { + "copyright": "Copyright © Luo Ran 2017-{year}", + "copyrightContributors": "Gemaakt met <3 door GitHub-bijdragers" + }, + "commands": { + "mt": { + "hide": "MarkText verbergen", + "hideOthers": "Overige verbergen" + }, + "file": { + "newWindow": "Nieuw venster", + "newTab": "Nieuw tabblad", + "openFile": "Bestand openen", + "openFolder": "Map openen", + "save": "Opslaan", + "saveAs": "Opslaan als", + "moveFile": "Bestand verplaatsen", + "renameFile": "Bestand hernoemen", + "quickOpen": "Snel openen", + "changeEncoding": "Codering wijzigen", + "changeLineEnding": "Regeleinde wijzigen", + "trailingNewline": "Afsluitende lege regel", + "print": "Afdrukken", + "preferences": "Voorkeuren", + "closeTab": "Tabblad sluiten", + "closeWindow": "Venster sluiten", + "quit": "Afsluiten", + "toggleAutoSave": "Automatisch opslaan in-/uitschakelen", + "importFile": "Bestand importeren", + "exportFile": "Bestand exporteren", + "exportFilePdf": "Exporteren als PDF", + "zoom": "Zoom", + "checkUpdate": "Controleer op updates" + }, + "edit": { + "undo": "Ongedaan maken", + "redo": "Opnieuw", + "cut": "Knippen", + "copy": "Kopiëren", + "paste": "Plakken", + "copyAsRich": "Kopiëren als opgemaakte tekst", + "copyAsHtml": "Kopiëren als HTML", + "pasteAsPlaintext": "Plakken als platte tekst", + "selectAll": "Alles selecteren", + "duplicate": "Dupliceren", + "createParagraph": "Alinea aanmaken", + "deleteParagraph": "Alinea verwijderen", + "find": "Zoeken", + "findNext": "Volgende zoeken", + "findPrevious": "Vorige zoeken", + "replace": "Vervangen", + "findInFolder": "Zoeken in map", + "screenshot": "Schermafbeelding" + }, + "paragraph": { + "heading1": "Kop 1", + "heading2": "Kop 2", + "heading3": "Kop 3", + "heading4": "Kop 4", + "heading5": "Kop 5", + "heading6": "Kop 6", + "upgradeHeading": "Kop promoveren", + "degradeHeading": "Kop degraderen", + "table": "Tabel", + "codeFence": "Codeblok", + "quoteBlock": "Citaatblok", + "mathFormula": "Wiskundeformule", + "htmlBlock": "HTML-blok", + "orderList": "Genummerde lijst", + "bulletList": "Opsommingslijst", + "taskList": "Takenlijst", + "looseListItem": "Los lijstitem", + "paragraph": "Alinea", + "horizontalLine": "Horizontale lijn", + "frontMatter": "Front Matter", + "resetParagraph": "Alinea herstellen", + "mathBlock": "Wiskundeblok", + "horizontalRule": "Horizontale lijn" + }, + "format": { + "strong": "Vet", + "emphasis": "Cursief", + "underline": "Onderstreept", + "superscript": "Superscript", + "subscript": "Subscript", + "highlight": "Markering", + "inlineCode": "Inline code", + "inlineMath": "Inline wiskunde", + "strike": "Doorgestreept", + "hyperlink": "Hyperlink", + "image": "Afbeelding", + "clearFormat": "Opmaak wissen" + }, + "window": { + "minimize": "Minimaliseren", + "close": "Sluiten", + "toggleAlwaysOnTop": "Altijd bovenaan in-/uitschakelen", + "zoomIn": "Inzoomen", + "zoomOut": "Uitzoomen", + "toggleFullScreen": "Volledig scherm in-/uitschakelen", + "changeTheme": "Thema wijzigen" + }, + "view": { + "commandPalette": "Opdrachtpalet", + "sourceCodeMode": "Broncodeweergave", + "typewriterMode": "Typemachinestand", + "focusMode": "Focusstand", + "toggleSourceCodeMode": "Broncodeweergave in-/uitschakelen", + "toggleTypewriterMode": "Typemachinestand in-/uitschakelen", + "toggleFocusMode": "Focusstand in-/uitschakelen", + "toggleSidebar": "Zijbalk in-/uitschakelen", + "toggleToc": "Inhoudsopgave in-/uitschakelen", + "toggleTabbar": "Tabbalk in-/uitschakelen", + "toggleDevTools": "Ontwikkelaarshulpmiddelen in-/uitschakelen", + "devReload": "Ontwikkelaar herladen", + "reloadImages": "Afbeeldingen herladen", + "textDirection": "Tekstrichting", + "actualSize": "Werkelijke grootte", + "devToggleDeveloperTools": "Ontwikkelaarshulpmiddelen in-/uitschakelen" + }, + "tabs": { + "cycleForward": "Volgende tabblad", + "cycleBackward": "Vorige tabblad", + "switchToLeft": "Naar links wisselen", + "switchToRight": "Naar rechts wisselen", + "switchToFirst": "Naar eerste wisselen", + "switchToSecond": "Naar tweede wisselen", + "switchToThird": "Naar derde wisselen", + "switchToFourth": "Naar vierde wisselen", + "switchToFifth": "Naar vijfde wisselen", + "switchToSixth": "Naar zesde wisselen", + "switchToSeventh": "Naar zevende wisselen", + "switchToEighth": "Naar achtste wisselen", + "switchToNinth": "Naar negende wisselen", + "switchToTenth": "Naar tiende wisselen" + }, + "docs": { + "userGuide": "Gebruikershandleiding", + "markdownSyntax": "Markdown-syntax" + }, + "spellchecker": { + "switchLanguage": "Taal wisselen" + } + }, + "common": { + "cancel": "Annuleren", + "ok": "OK" + }, + "dialog": { + "cancel": "Annuleren", + "changesWillBeLost": "Wijzigingen gaan verloren", + "close": "Sluiten", + "dontSave": "Niet opslaan", + "file": "Bestand", + "fileExists": "Bestand bestaat al: {filename}", + "files": "Bestanden", + "importWarning": "Importwaarschuwing", + "installPandoc": "Pandoc installeren", + "keepOpen": "Open houden", + "replace": "Vervangen", + "save": "Opslaan", + "saveChanges": "Wijzigingen opslaan", + "saveFailure": "Opslaan mislukt", + "unsafeFileTitle": "Mogelijk onveilig bestand", + "unsafeFileMessage": "Deze link opent een bestand dat code kan uitvoeren", + "unsafeFileDetail": "\"{name}\" is een uitvoerbaar bestand of script. Het openen kan programma's op je computer uitvoeren. Ga alleen door als je dit document vertrouwt.", + "openAnyway": "Toch openen" + }, + "error": { + "configSchemaViolation": "Configuratieschemaovertreding", + "copyError": "Kopieerfout", + "initializationFailed": "Initialisatie mislukt {hint}", + "otherInstanceDetected": "Er draait al een andere instantie van MarkText", + "report": "Melden", + "startupError": "Opstartfout", + "terminatedDueToError": "Beëindigd door fout", + "unexpectedErrorWithMessage": "Onverwachte fout: {message}", + "unexpectedMainProcess": "Onverwachte fout in hoofdproces", + "unexpectedRendererProcess": "Onverwachte fout in rendererproces" + }, + "notifications": { + "defaultMessage": "Standaardbericht", + "defaultTitle": "Melding" + }, + "store": { + "editor": { + "anchorLinkCopied": "Ankerlink gekopieerd", + "tabNotFound": "Tabblad niet gevonden", + "tocItemNotFound": "Inhoudsopgave-item {key} niet gevonden", + "errorLoadingTabTitle": "Fout bij laden tabblad", + "errorLoadingTabMessage": "Er is een fout opgetreden bij het laden van de bestandswijziging omdat het tabblad niet gevonden kan worden.", + "mixedLineEndingsNormalized": "\"{name}\" heeft gemengde regeleindes die automatisch genormaliseerd zijn naar {lineEnding}.", + "imageDeletionUrlTitle": "Verwijder-URL afbeelding", + "imageDeletionUrlMessage": "Klik om de verwijder-URL van de geüploade afbeelding naar het klembord te kopiëren {url}", + "errorWhileSaving": "Er is een fout opgetreden bij het opslaan: {msg}", + "exportSuccessTitle": "Succesvol geëxporteerd", + "exportSuccessMessage": "\"{name}\" is succesvol geëxporteerd!", + "fileRemovedOnDisk": "\"{name}\" is verwijderd van schijf.", + "fileChangedOnDisk": "\"{name}\" is gewijzigd op schijf. Wil je het opnieuw laden?" + } + }, + "spellchecker": { + "failedToRemoveWord": "Woord verwijderen uit woordenboek mislukt", + "unexpectedError": "Onverwachte fout in spellingcontrole" + }, + "editor": { + "export": { + "error": "Exportfout", + "errorExporting": "Fout bij exporteren van {type}-bestand", + "failed": "{type}-export mislukt" + }, + "insertTable": { + "title": "Tabel invoegen", + "rows": "Rijen", + "columns": "Kolommen" + }, + "notifications": { + "notificationNotFound": "Melding niet gevonden" + }, + "print": { + "error": "Afdrukfout voor \"{title}\"", + "failed": "Afdrukken mislukt" + }, + "sourceCode": { + "cursorNullComment": "Cursor null opmerking", + "imageStructureDeletedComment": "Afbeeldingsstructuur verwijderd opmerking" + }, + "spellcheck": { + "disabledError": "Spellingcontrole uitgeschakeld fout", + "errorSwitchingLanguage": "Fout bij wisselen van spellingcontroletaal {languageCode}", + "languageMissing": "Spellingcontroletaal ontbreekt {languageCode}", + "switchError": "Spellingcontrole wisselfout voor {languageCode}, {error}", + "title": "Spellingcontrole" + } + } +} diff --git a/packages/desktop/static/locales/pt.json b/packages/desktop/static/locales/pt.json index c8f4aceb1c..ae93c3746c 100644 --- a/packages/desktop/static/locales/pt.json +++ b/packages/desktop/static/locales/pt.json @@ -241,7 +241,9 @@ "openFolder": "Abrir Pasta" }, "toc": { - "title": "Índice" + "title": "Índice", + "expandAll": "Expandir tudo", + "collapseAll": "Recolher tudo" }, "icons": { "files": "Arquivos", @@ -497,6 +499,7 @@ "german": "Deutsch", "japanese": "日本語", "korean": "한국어", + "dutch": "Nederlands", "portuguese": "Português", "turkish": "Türkçe" } @@ -579,7 +582,7 @@ }, "uploader": { "title": "Carregador de Imagens", - "currentUploader": "Carregador Atual", + "currentUploader": "Uploader atual: {name}", "picgoNotInstalled": "PicGo não está instalado", "picgoInstalled": "PicGo está instalado", "picgoDetectionFailed": "Detecção do PicGo falhou", @@ -699,7 +702,7 @@ "online": "Online", "saveError": "Erro ao salvar atalhos de teclado", "shortcutInUse": "Atalho em uso", - "shortcutInUseMessage": "Este atalho já está atribuído a outro comando", + "shortcutInUseMessage": "Este atalho já está atribuído a {accelerator}", "table": { "description": "Descrição", "edit": "Editar", @@ -728,7 +731,7 @@ } }, "about": { - "copyright": "Copyright", + "copyright": "Copyright © Luo Ran 2017-{year}", "copyrightContributors": "Copyright Contribuidores" }, "commands": { @@ -881,7 +884,7 @@ "close": "Fechar", "dontSave": "Não Salvar", "file": "Arquivo", - "fileExists": "Arquivo existe", + "fileExists": "O arquivo já existe: {filename}", "files": "Arquivos", "importWarning": "Aviso de importação", "installPandoc": "Instalar Pandoc", @@ -898,12 +901,12 @@ "error": { "configSchemaViolation": "Violação do esquema de configuração", "copyError": "Erro de cópia", - "initializationFailed": "Falha na inicialização", + "initializationFailed": "Falha na inicialização {hint}", "otherInstanceDetected": "Outra instância do MarkText já está em execução", "report": "Reportar", "startupError": "Erro de inicialização", "terminatedDueToError": "Terminado devido a erro", - "unexpectedErrorWithMessage": "Erro inesperado com mensagem", + "unexpectedErrorWithMessage": "Erro inesperado: {message}", "unexpectedMainProcess": "Erro inesperado do processo principal", "unexpectedRendererProcess": "Erro inesperado do processo de renderização" }, @@ -915,7 +918,7 @@ "editor": { "anchorLinkCopied": "Link âncora copiado", "tabNotFound": "Aba não encontrada", - "tocItemNotFound": "Item do índice não encontrado", + "tocItemNotFound": "Item do índice {key} não encontrado", "errorLoadingTabTitle": "Erro ao carregar a aba", "errorLoadingTabMessage": "Ocorreu um erro ao carregar a alteração do arquivo porque a aba não pôde ser encontrada.", "mixedLineEndingsNormalized": "“{name}” possui finais de linha mistos que foram normalizados automaticamente para {lineEnding}.", @@ -935,8 +938,8 @@ "editor": { "export": { "error": "Erro de exportação", - "errorExporting": "Erro ao exportar arquivo", - "failed": "Exportação falhou" + "errorExporting": "Erro ao exportar arquivo {type}", + "failed": "Falha na exportação {type}" }, "insertTable": { "title": "Inserir Tabela", @@ -947,7 +950,7 @@ "notificationNotFound": "Notificação não encontrada" }, "print": { - "error": "Erro de impressão", + "error": "Erro de impressão para \"{title}\"", "failed": "Impressão falhou" }, "sourceCode": { @@ -956,9 +959,9 @@ }, "spellcheck": { "disabledError": "Erro ao desativar verificação ortográfica", - "errorSwitchingLanguage": "Erro ao trocar idioma da verificação ortográfica", - "languageMissing": "Idioma da verificação ortográfica não encontrado", - "switchError": "Erro ao trocar verificação ortográfica", + "errorSwitchingLanguage": "Erro ao alternar idioma do corretor {languageCode}", + "languageMissing": "Idioma do corretor não encontrado {languageCode}", + "switchError": "Erro ao alternar corretor para {languageCode}, {error}", "title": "Verificação Ortográfica" } } diff --git a/packages/desktop/static/locales/tr.json b/packages/desktop/static/locales/tr.json index 27f46a2a69..eac624c725 100644 --- a/packages/desktop/static/locales/tr.json +++ b/packages/desktop/static/locales/tr.json @@ -241,7 +241,9 @@ "openFolder": "Klasör Aç" }, "toc": { - "title": "İçindekiler" + "title": "İçindekiler", + "expandAll": "Tümünü genişlet", + "collapseAll": "Tümünü daralt" }, "icons": { "files": "Dosyalar", @@ -497,6 +499,7 @@ "german": "Deutsch", "japanese": "日本語", "korean": "한국어", + "dutch": "Nederlands", "portuguese": "Português", "turkish": "Türkçe" } diff --git a/packages/desktop/static/locales/zh-CN.json b/packages/desktop/static/locales/zh-CN.json index d32ecab6fa..619f3f0105 100644 --- a/packages/desktop/static/locales/zh-CN.json +++ b/packages/desktop/static/locales/zh-CN.json @@ -241,7 +241,9 @@ "openFolder": "打开文件夹" }, "toc": { - "title": "目录" + "title": "目录", + "expandAll": "全部展开", + "collapseAll": "全部折叠" }, "icons": { "files": "文件", @@ -497,6 +499,7 @@ "german": "Deutsch", "japanese": "日本語", "korean": "한국어", + "dutch": "Nederlands", "portuguese": "Português", "turkish": "Türkçe" } @@ -579,7 +582,7 @@ }, "uploader": { "title": "图片上传器", - "currentUploader": "当前上传器", + "currentUploader": "当前上传器:{name}", "picgoNotInstalled": "PicGo 未安装", "picgoInstalled": "PicGo 已安装", "picgoDetectionFailed": "PicGo 检测失败", @@ -699,7 +702,7 @@ "online": "在线", "saveError": "保存快捷键时出错", "shortcutInUse": "快捷键正在使用", - "shortcutInUseMessage": "此快捷键已分配给另一个命令", + "shortcutInUseMessage": "此快捷键已分配给 {accelerator}", "table": { "description": "描述", "edit": "编辑", @@ -728,7 +731,7 @@ } }, "about": { - "copyright": "版权", + "copyright": "Copyright © Luo Ran 2017-{year}", "copyrightContributors": "版权贡献者" }, "commands": { @@ -881,7 +884,7 @@ "close": "关闭", "dontSave": "放弃保存", "file": "文件", - "fileExists": "文件已存在", + "fileExists": "文件已存在:{filename}", "files": "文件", "importWarning": "导入警告", "installPandoc": "安装 Pandoc", @@ -898,12 +901,12 @@ "error": { "configSchemaViolation": "配置架构违规", "copyError": "复制错误", - "initializationFailed": "初始化失败", + "initializationFailed": "初始化失败 {hint}", "otherInstanceDetected": "检测到 MarkText 的另一个实例正在运行", "report": "报告", "startupError": "启动错误", "terminatedDueToError": "由于错误而终止", - "unexpectedErrorWithMessage": "意外错误消息", + "unexpectedErrorWithMessage": "意外错误:{message}", "unexpectedMainProcess": "意外的主进程错误", "unexpectedRendererProcess": "意外的渲染进程错误" }, @@ -915,7 +918,7 @@ "editor": { "anchorLinkCopied": "锚点链接已复制", "tabNotFound": "未找到标签页", - "tocItemNotFound": "未找到目录项", + "tocItemNotFound": "目录项 {key} 未找到", "errorLoadingTabTitle": "加载标签页出错", "errorLoadingTabMessage": "加载文件变更时发生错误,因为找不到该标签页。", "mixedLineEndingsNormalized": "“{name}” 含有混合换行符,已自动规范化为 {lineEnding}。", @@ -947,19 +950,19 @@ }, "export": { "error": "导出错误", - "errorExporting": "导出文件时出错", - "failed": "导出失败" + "errorExporting": "导出 {type} 文件时出错", + "failed": "{type} 导出失败" }, "print": { - "error": "打印错误", + "error": "打印 \"{title}\" 时出错", "failed": "打印失败" }, "spellcheck": { "title": "拼写检查", "disabledError": "拼写检查已禁用错误", - "errorSwitchingLanguage": "切换拼写检查语言时出错", - "languageMissing": "拼写检查语言缺失", - "switchError": "拼写检查切换错误" + "errorSwitchingLanguage": "切换拼写检查语言时出错 {languageCode}", + "languageMissing": "拼写检查语言缺失 {languageCode}", + "switchError": "{languageCode} 拼写检查切换错误,{error}" } } } diff --git a/packages/desktop/static/locales/zh-TW.json b/packages/desktop/static/locales/zh-TW.json index 17621c18c0..f78f65c763 100644 --- a/packages/desktop/static/locales/zh-TW.json +++ b/packages/desktop/static/locales/zh-TW.json @@ -241,7 +241,9 @@ "openFolder": "開啟資料夾" }, "toc": { - "title": "目錄" + "title": "目錄", + "expandAll": "全部展開", + "collapseAll": "全部折疊" }, "icons": { "files": "檔案", @@ -497,6 +499,7 @@ "german": "Deutsch", "japanese": "日本語", "korean": "한국어", + "dutch": "Nederlands", "portuguese": "Português", "turkish": "Türkçe" } @@ -579,7 +582,7 @@ }, "uploader": { "title": "圖片上傳器", - "currentUploader": "目前上傳器", + "currentUploader": "目前上傳器:{name}", "picgoNotInstalled": "PicGo 未安裝", "picgoInstalled": "PicGo 已安裝", "picgoDetectionFailed": "PicGo 偵測失敗", @@ -699,7 +702,7 @@ "online": "線上", "saveError": "儲存按鍵綁定時發生錯誤", "shortcutInUse": "快捷鍵使用中", - "shortcutInUseMessage": "此快捷鍵已指派給另一個命令", + "shortcutInUseMessage": "此快捷鍵已指派給 {accelerator}", "table": { "description": "描述", "edit": "編輯", @@ -728,7 +731,7 @@ } }, "about": { - "copyright": "版權", + "copyright": "Copyright © Luo Ran 2017-{year}", "copyrightContributors": "版權貢獻者" }, "commands": { @@ -881,7 +884,7 @@ "close": "關閉", "dontSave": "不儲存", "file": "檔案", - "fileExists": "檔案已存在", + "fileExists": "檔案已存在:{filename}", "files": "檔案", "importWarning": "匯入警告", "installPandoc": "安裝 Pandoc", @@ -898,12 +901,12 @@ "error": { "configSchemaViolation": "設定結構違規", "copyError": "複製錯誤", - "initializationFailed": "初始化失敗", + "initializationFailed": "初始化失敗 {hint}", "otherInstanceDetected": "偵測到另一個 MarkText 實例正在執行", "report": "回報", "startupError": "啟動錯誤", "terminatedDueToError": "因錯誤而終止", - "unexpectedErrorWithMessage": "意外錯誤訊息", + "unexpectedErrorWithMessage": "意外錯誤:{message}", "unexpectedMainProcess": "意外的主程序錯誤", "unexpectedRendererProcess": "意外的渲染程序錯誤" }, @@ -915,7 +918,7 @@ "editor": { "anchorLinkCopied": "錨點連結已複製", "tabNotFound": "找不到標籤頁", - "tocItemNotFound": "找不到目錄項目", + "tocItemNotFound": "目錄項目 {key} 未找到", "errorLoadingTabTitle": "載入標籤頁發生錯誤", "errorLoadingTabMessage": "載入檔案變更時發生錯誤,因為找不到該標籤頁。", "mixedLineEndingsNormalized": "「{name}」含有混合換行字元,已自動正規化為 {lineEnding}。", @@ -935,8 +938,8 @@ "editor": { "export": { "error": "匯出錯誤", - "errorExporting": "匯出檔案時發生錯誤", - "failed": "匯出失敗" + "errorExporting": "匯出 {type} 檔案時發生錯誤", + "failed": "{type} 匯出失敗" }, "insertTable": { "title": "插入表格", @@ -947,7 +950,7 @@ "notificationNotFound": "找不到通知" }, "print": { - "error": "列印錯誤", + "error": "列印 \"{title}\" 時發生錯誤", "failed": "列印失敗" }, "sourceCode": { @@ -956,9 +959,9 @@ }, "spellcheck": { "disabledError": "拼字檢查停用錯誤", - "errorSwitchingLanguage": "切換拼字檢查語言時發生錯誤", - "languageMissing": "拼字檢查語言遺失", - "switchError": "拼字檢查切換錯誤", + "errorSwitchingLanguage": "切換拼字檢查語言時發生錯誤 {languageCode}", + "languageMissing": "拼字檢查語言缺失 {languageCode}", + "switchError": "{languageCode} 拼字檢查切換錯誤,{error}", "title": "拼字檢查" } } diff --git a/packages/desktop/test/unit/specs/locale-validation.spec.ts b/packages/desktop/test/unit/specs/locale-validation.spec.ts new file mode 100644 index 0000000000..73984386ea --- /dev/null +++ b/packages/desktop/test/unit/specs/locale-validation.spec.ts @@ -0,0 +1,197 @@ +import { describe, expect, it } from 'vitest' +import fs from 'fs' +import path from 'path' + +/** + * Desktop locale validation. + * + * Ensures all JSON locale files stay consistent with the English source (en.json). + * When this test fails, the error message tells you exactly what to fix and where. + * + * Checks: + * 1. Key parity — every locale must have the same nested keys as en.json + * 2. Placeholders — translated strings must preserve {variable} placeholders + * 3. Tech terms — product names (Front Matter, Mermaid, PlantUML, Vega) stay English + * 4. No empties — no leaf value may be an empty string + * + * How to fix failures: + * - Missing key: Copy the key from en.json into .json and translate the value. + * - Extra key: Remove the key from .json (it doesn't exist in en.json). + * - Placeholder: Ensure the translated string contains the same {variable} tokens as en.json. + * - Tech term: Keep the product name in English (e.g. "Front Matter", not "Métadonnées"). + * - Empty value: Add a translation (or copy the English value as fallback). + * + * Canonical spelling of product/standard names: + * - PlantUML (not "Plantuml") — https://plantuml.com + * - Mermaid — https://mermaid.js.org + * - Vega-Lite (not "Vega Chart") — https://vega.github.io/vega-lite/ + * - Front Matter — YAML metadata block (industry standard term) + * - KaTeX (not "Katex") — https://katex.org + * - MathJax (not "Mathjax") — https://www.mathjax.org + */ + +const LOCALES_DIR = path.join(__dirname, '../../../static/locales') + +const loadLocale = (lang: string): Record => + JSON.parse(fs.readFileSync(path.join(LOCALES_DIR, `${lang}.json`), 'utf8')) + +// Recursively collect all leaf keys as dot-separated paths +const collectKeys = (obj: unknown, prefix = ''): string[] => { + if (obj === null || typeof obj !== 'object') return [prefix] + const keys: string[] = [] + for (const [key, value] of Object.entries(obj as Record)) { + const fullKey = prefix ? `${prefix}.${key}` : key + if (typeof value === 'object' && value !== null && !Array.isArray(value)) { + keys.push(...collectKeys(value, fullKey)) + } else { + keys.push(fullKey) + } + } + return keys +} + +// Extract {placeholder} variables from a string +const extractPlaceholders = (str: string): string[] => { + if (typeof str !== 'string') return [] + const matches = str.match(/\{[^}]+\}/g) + return matches ? matches.sort() : [] +} + +// Get a nested value by dot-separated path +const getByPath = (obj: unknown, dotPath: string): unknown => { + let current: unknown = obj + for (const part of dotPath.split('.')) { + if (current === null || typeof current !== 'object') return undefined + current = (current as Record)[part] + } + return current +} + +// Technical terms that must stay in English for Latin-script locales. +// CJK locales may translate these (different convention). +const TECHNICAL_TERMS: Array<{ path: string; mustContain: string }> = [ + { path: 'menu.paragraph.frontMatter', mustContain: 'Front Matter' }, + { path: 'quickInsert.frontMatter.title', mustContain: 'Front Matter' }, + { path: 'quickInsert.mermaid.title', mustContain: 'Mermaid' }, + { path: 'quickInsert.plantUMLChart.title', mustContain: 'PlantUML' }, + { path: 'quickInsert.vegaChart.title', mustContain: 'Vega' }, +] + +const LATIN_SCRIPT_LOCALES = ['de', 'es', 'fr', 'nl', 'pt'] + +const getAvailableLocales = (): string[] => + fs.readdirSync(LOCALES_DIR) + .filter(f => f.endsWith('.json') && !f.endsWith('.min.json') && f !== 'en.json') + .map(f => f.replace('.json', '')) + +describe('desktop locale validation', () => { + const en = loadLocale('en') + const enKeys = collectKeys(en).sort() + const locales = getAvailableLocales() + + describe('key parity with en.json', () => { + for (const lang of locales) { + it(`${lang}.json has the same keys as en.json`, () => { + const locale = loadLocale(lang) + const localeKeys = new Set(collectKeys(locale)) + const missing = enKeys.filter(k => !localeKeys.has(k)) + const extra = [...localeKeys].filter(k => !enKeys.includes(k)) + + const hints: string[] = [] + if (missing.length) { + hints.push( + `\n MISSING (${missing.length}) — copy these from en.json into ${lang}.json and translate:`, + ...missing.map(k => ` "${k}": ${JSON.stringify(getByPath(en, k))}`) + ) + } + if (extra.length) { + hints.push( + `\n EXTRA (${extra.length}) — remove these from ${lang}.json (not in en.json):`, + ...extra.map(k => ` "${k}"`) + ) + } + + expect( + missing.length + extra.length, + hints.join('\n') + ).toBe(0) + }) + } + }) + + describe('placeholder variables', () => { + for (const lang of locales) { + it(`${lang}.json preserves all {variable} placeholders`, () => { + const locale = loadLocale(lang) + const localeKeys = new Set(collectKeys(locale)) + const issues: string[] = [] + + for (const key of enKeys) { + if (!localeKeys.has(key)) continue + const enValue = getByPath(en, key) as string + const localeValue = getByPath(locale, key) as string + if (typeof enValue !== 'string' || typeof localeValue !== 'string') continue + + const enPh = extractPlaceholders(enValue) + const localePh = extractPlaceholders(localeValue) + if (enPh.length > 0 && JSON.stringify(enPh) !== JSON.stringify(localePh)) { + issues.push( + ` "${key}":` + + `\n en.json: ${JSON.stringify(enValue)}` + + `\n ${lang}.json: ${JSON.stringify(localeValue)}` + + `\n expected placeholders: ${enPh.join(' ')}` + + `\n found placeholders: ${localePh.join(' ') || '(none)'}` + + `\n → Add the missing {variables} to the ${lang} translation.\n` + ) + } + } + + expect(issues.length, `\n Placeholder issues in ${lang}.json:\n\n${issues.join('\n')}`).toBe(0) + }) + } + }) + + describe('technical terms stay in English (Latin-script locales)', () => { + const latinLocales = locales.filter(l => LATIN_SCRIPT_LOCALES.includes(l)) + for (const lang of latinLocales) { + it(`${lang}.json keeps product names untranslated`, () => { + const locale = loadLocale(lang) + const violations: string[] = [] + + for (const { path: keyPath, mustContain } of TECHNICAL_TERMS) { + const value = getByPath(locale, keyPath) + if (typeof value === 'string' && !value.includes(mustContain)) { + violations.push( + ` "${keyPath}": "${value}"` + + `\n → Must contain "${mustContain}" (it's a product name, not a translatable word).` + ) + } + } + + expect( + violations.length, + `\n Technical terms incorrectly translated in ${lang}.json:\n\n${violations.join('\n\n')}` + ).toBe(0) + }) + } + }) + + describe('no empty values', () => { + for (const lang of locales) { + it(`${lang}.json has no empty strings`, () => { + const locale = loadLocale(lang) + const allKeys = collectKeys(locale) + const empty = allKeys.filter(k => { + const val = getByPath(locale, k) + return typeof val === 'string' && val.trim() === '' + }) + + expect( + empty.length, + `\n Empty values in ${lang}.json (add a translation or copy from en.json):\n` + + empty.map(k => ` "${k}": ${JSON.stringify(getByPath(en, k))}`).join('\n') + ).toBe(0) + }) + } + }) +}) diff --git a/packages/muya/e2e/tests/ui/slash-menu.spec.ts b/packages/muya/e2e/tests/ui/slash-menu.spec.ts index fdcfa1dbf6..d31bd20fba 100644 --- a/packages/muya/e2e/tests/ui/slash-menu.spec.ts +++ b/packages/muya/e2e/tests/ui/slash-menu.spec.ts @@ -29,6 +29,44 @@ test.describe('slash quick-insert menu', () => { await expect(page.locator(editor.atxHeading).first()).toBeVisible(); }); + test('highlight follows the active mouse or keyboard input', async ({ page }) => { + await page.evaluate(() => window.muya!.setContent('')); + await page.locator(editor.paragraph).first().click(); + await page.keyboard.type('/'); + + const menu = page.locator(floats.quickInsert); + const heading1 = page.locator(quickInsertItem('atx-heading 1')); + await heading1.hover(); + + const pointerBackgrounds = await page.evaluate(() => { + const paragraph = document.querySelector('[data-label="paragraph"]'); + const heading1 = document.querySelector('[data-label="atx-heading 1"]'); + return { + paragraph: paragraph && getComputedStyle(paragraph).backgroundColor, + heading1: heading1 && getComputedStyle(heading1).backgroundColor, + }; + }); + expect(pointerBackgrounds.heading1).not.toBe(pointerBackgrounds.paragraph); + + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('ArrowDown'); + + await expect(menu).toHaveClass(/mu-keyboard-navigation/); + await expect(page.locator(quickInsertItem('atx-heading 3'))).toHaveClass(/active/); + const keyboardBackgrounds = await page.evaluate(() => { + const heading1 = document.querySelector('[data-label="atx-heading 1"]'); + const heading3 = document.querySelector('[data-label="atx-heading 3"]'); + return { + heading1: heading1 && getComputedStyle(heading1).backgroundColor, + heading3: heading3 && getComputedStyle(heading3).backgroundColor, + }; + }); + expect(keyboardBackgrounds.heading3).not.toBe(keyboardBackgrounds.heading1); + + await page.keyboard.press('Enter'); + await expect(page.locator('h3.mu-atx-heading').first()).toBeVisible(); + }); + // EXTEND item 1a — Enter-select inserts a block (keyboard, not click). // Typing `/` then narrowing with `quote` filters the menu so `block-quote` // becomes the active item (fuse top match); BaseScrollFloat's keydown diff --git a/packages/muya/src/__tests__/localeCompleteness.spec.ts b/packages/muya/src/__tests__/localeCompleteness.spec.ts index 4d904a9ef0..3b1c097e62 100644 --- a/packages/muya/src/__tests__/localeCompleteness.spec.ts +++ b/packages/muya/src/__tests__/localeCompleteness.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { de, en, es, fr, ja, ko, pt, zhCN, zhTW } from '../locales'; +import { de, en, es, fr, ja, ko, nl, pt, zhCN, zhTW } from '../locales'; // CHARACTERIZATION: every shipped locale must carry the exact same translation // keys as the canonical `en` locale (no missing/extra keys), and expose a @@ -12,11 +12,36 @@ const nonEnLocales: Array<[string, typeof en]> = [ ['fr', fr], ['ja', ja], ['ko', ko], + ['nl', nl], ['pt', pt], ['zh-CN', zhCN], ['zh-TW', zhTW], ]; +// Technical terms that MUST NOT be translated — they are product names, +// standards, or widely-recognized English terms used as-is in all locales. +// Note: CJK locales (ja, ko, zh-CN, zh-TW) and Turkish conventionally +// translate these terms. We only enforce this for Latin-script locales. +// +// Canonical spelling of product/standard names (for reference): +// - PlantUML (not "Plantuml") — https://plantuml.com +// - Mermaid — https://mermaid.js.org +// - Vega-Lite (not "Vega Chart") — https://vega.github.io/vega-lite/ +// - Front Matter — YAML metadata block (industry standard term) +// - KaTeX (not "Katex") — https://katex.org +// - MathJax (not "Mathjax") — https://www.mathjax.org +// +// NOTE: The locale key for PlantUML is misspelled as 'Plantuml' in the source. +// This is a known upstream issue — the key cannot be changed without a +// coordinated refactor, but display values SHOULD use the correct casing. +const TECHNICAL_TERMS: Record = { + 'Front Matter': 'Front Matter', + 'Mermaid': 'Mermaid', + 'Plantuml': 'PlantUML', // key is misspelled upstream; display value uses correct casing +}; + +const LATIN_SCRIPT_LOCALES = ['de', 'es', 'fr', 'nl', 'pt']; + describe('locale completeness', () => { const enKeys = Object.keys(en.resource).sort(); @@ -25,8 +50,8 @@ describe('locale completeness', () => { expect(enKeys.length).toBeGreaterThan(0); }); - it('ships exactly nine built-in locales (en + 8 translations)', () => { - expect(nonEnLocales).toHaveLength(8); + it('ships exactly ten built-in locales (en + 9 translations)', () => { + expect(nonEnLocales).toHaveLength(9); }); describe('key parity with en', () => { @@ -44,6 +69,7 @@ describe('locale completeness', () => { 'fr': 'fr', 'ja': 'ja', 'ko': 'ko', + 'nl': 'nl', 'pt': 'pt', 'zh-CN': 'zh-CN', 'zh-TW': 'zh-TW', @@ -65,4 +91,18 @@ describe('locale completeness', () => { }); } }); + + describe('technical terms are not translated (Latin-script locales)', () => { + const latinLocales = nonEnLocales.filter(([tag]) => LATIN_SCRIPT_LOCALES.includes(tag)); + for (const [tag, locale] of latinLocales) { + for (const [key, expectedValue] of Object.entries(TECHNICAL_TERMS)) { + it(`${tag} keeps "${key}" untranslated`, () => { + const resource = locale.resource as Record; + if (key in resource) { + expect(resource[key]).toBe(expectedValue); + } + }); + } + } + }); }); diff --git a/packages/muya/src/locales/de.ts b/packages/muya/src/locales/de.ts index 49caad7d1f..cc3fcd46a0 100644 --- a/packages/muya/src/locales/de.ts +++ b/packages/muya/src/locales/de.ts @@ -32,7 +32,7 @@ export const de = { 'To-do List': 'Aufgabenliste', 'Vega Chart': 'Vega-Diagramm', 'Mermaid': 'Mermaid', - 'Plantuml': 'Plantuml', + 'Plantuml': 'PlantUML', 'basic blocks': 'Grundblöcke', 'headings': 'Überschriften', 'advanced blocks': 'Erweiterte Blöcke', diff --git a/packages/muya/src/locales/es.ts b/packages/muya/src/locales/es.ts index 1d1dd251a1..6c8154e631 100644 --- a/packages/muya/src/locales/es.ts +++ b/packages/muya/src/locales/es.ts @@ -32,7 +32,7 @@ export const es = { 'To-do List': 'Lista de tareas', 'Vega Chart': 'Gráfico Vega', 'Mermaid': 'Mermaid', - 'Plantuml': 'Plantuml', + 'Plantuml': 'PlantUML', 'basic blocks': 'bloques básicos', 'headings': 'encabezados', 'advanced blocks': 'bloques avanzados', diff --git a/packages/muya/src/locales/fr.ts b/packages/muya/src/locales/fr.ts index 622b4cd20e..936d455930 100644 --- a/packages/muya/src/locales/fr.ts +++ b/packages/muya/src/locales/fr.ts @@ -32,7 +32,7 @@ export const fr = { 'To-do List': 'Liste de tâches', 'Vega Chart': 'Graphique Vega', 'Mermaid': 'Mermaid', - 'Plantuml': 'Plantuml', + 'Plantuml': 'PlantUML', 'basic blocks': 'blocs de base', 'headings': 'titres', 'advanced blocks': 'blocs avancés', diff --git a/packages/muya/src/locales/index.ts b/packages/muya/src/locales/index.ts index 5f51cc59af..b9f7eb555d 100644 --- a/packages/muya/src/locales/index.ts +++ b/packages/muya/src/locales/index.ts @@ -4,6 +4,7 @@ export { es } from './es'; export { fr } from './fr'; export { ja } from './ja'; export { ko } from './ko'; +export { nl } from './nl'; export { pt } from './pt'; export { tr } from './tr'; export { zhCN } from './zh-CN'; diff --git a/packages/muya/src/locales/nl.ts b/packages/muya/src/locales/nl.ts new file mode 100644 index 0000000000..b3ef11b10b --- /dev/null +++ b/packages/muya/src/locales/nl.ts @@ -0,0 +1,101 @@ +export const nl = { + name: 'nl', + resource: { + // tableTools + 'Insert Row Above': 'Rij invoegen boven', + 'Insert Row Below': 'Rij invoegen onder', + 'Remove Row': 'Rij verwijderen', + // tableColumnTools + 'Align Left': 'Links uitlijnen', + 'Align Center': 'Centreren', + 'Align Right': 'Rechts uitlijnen', + 'Insert Column left': 'Kolom invoegen links', + 'Insert Column right': 'Kolom invoegen rechts', + 'Remove Column': 'Kolom verwijderen', + // quickInsert + 'Paragraph': 'Alinea', + 'Horizontal Line': 'Horizontale lijn', + 'Front Matter': 'Front Matter', + 'Heading 1': 'Kop 1', + 'Heading 2': 'Kop 2', + 'Heading 3': 'Kop 3', + 'Heading 4': 'Kop 4', + 'Heading 5': 'Kop 5', + 'Heading 6': 'Kop 6', + 'Table Block': 'Tabelblok', + 'Display Math': 'Wiskunde weergeven', + 'HTML Block': 'HTML-blok', + 'Code Block': 'Codeblok', + 'Quote Block': 'Citaatblok', + 'Order List': 'Genummerde lijst', + 'Bullet List': 'Opsommingslijst', + 'To-do List': 'Takenlijst', + 'Vega Chart': 'Vega-grafiek', + 'Mermaid': 'Mermaid', + 'Plantuml': 'PlantUML', + 'basic blocks': 'basisblokken', + 'headings': 'koppen', + 'advanced blocks': 'geavanceerde blokken', + 'list blocks': 'lijstblokken', + 'diagrams': 'diagrammen', + 'No result': 'Geen resultaat', + 'Search keyword...': 'Zoek trefwoord...', + 'Type / to insert...': 'Typ / om in te voegen...', + // formatPicker + 'Emphasize': 'Vet', + 'Italic': 'Cursief', + 'Underline': 'Onderstreept', + 'Strikethrough': 'Doorgestreept', + 'Highlight': 'Markering', + 'Inline Code': 'Inline code', + 'Inline Math': 'Inline wiskunde', + 'Link': 'Link', + 'Image': 'Afbeelding', + 'Eliminate': 'Opmaak wissen', + // Code block + 'Copy content': 'Inhoud kopiëren', + 'Input Language Identifier...': 'Voer taalidentificatie in...', + // emojiPicker + 'Smileys & Emotion': 'Smileys & Emotie', + 'People & Body': 'Mensen & Lichaam', + 'Animals & Nature': 'Dieren & Natuur', + 'Food & Drink': 'Eten & Drinken', + 'Travel & Places': 'Reizen & Plaatsen', + 'Activities': 'Activiteiten', + 'Objects': 'Objecten', + 'Symbols': 'Symbolen', + 'Flags': 'Vlaggen', + // frontMenu + 'Duplicate': 'Dupliceren', + 'New Paragraph': 'Nieuwe alinea', + 'Delete': 'Verwijderen', + // imageToolbar + 'Edit Image': 'Afbeelding bewerken', + 'Inline Image': 'Inline afbeelding', + 'Remove Image': 'Afbeelding verwijderen', + // ImageSelector + 'Alt text': 'Alt-tekst', + 'Image link or local path': 'Afbeeldingslink of lokaal pad', + 'Image title': 'Afbeeldingstitel', + 'Choose Image': 'Afbeelding kiezen', + 'Choose image from your computer.': 'Kies een afbeelding van je computer.', + 'Embed Image': 'Afbeelding insluiten', + 'Select': 'Selecteren', + 'Embed link': 'Link insluiten', + 'Paste web image or local image path. Use': 'Plak een webafbeelding of lokaal pad. Gebruik', + 'simple mode': 'eenvoudige modus', + 'full mode': 'volledige modus', + // preview block + 'Loading...': 'Laden...', + 'Invalid Diagram Code': 'Ongeldige diagramcode', + 'Empty Diagram': 'Leeg diagram', + 'Input Mathematical Formula...': 'Voer wiskundeformule in...', + 'Input Front Matter...': 'Voer Front Matter in...', + 'Invalid Mathematical Formula': 'Ongeldige wiskundeformule', + 'Empty Mathematical Formula': 'Lege wiskundeformule', + // headingCopyLink + 'Copy anchor link to this heading': 'Ankerlink naar deze kop kopiëren', + 'Click to add an image': 'Klik om een afbeelding toe te voegen', + 'Load image failed': 'Afbeelding laden mislukt', + }, +}; diff --git a/packages/muya/src/locales/pt.ts b/packages/muya/src/locales/pt.ts index bd54634d90..ee8bcd7127 100644 --- a/packages/muya/src/locales/pt.ts +++ b/packages/muya/src/locales/pt.ts @@ -32,7 +32,7 @@ export const pt = { 'To-do List': 'Lista de tarefas', 'Vega Chart': 'Gráfico Vega', 'Mermaid': 'Mermaid', - 'Plantuml': 'Plantuml', + 'Plantuml': 'PlantUML', 'basic blocks': 'blocos básicos', 'headings': 'títulos', 'advanced blocks': 'blocos avançados', diff --git a/packages/muya/src/ui/paragraphQuickInsertMenu/index.css b/packages/muya/src/ui/paragraphQuickInsertMenu/index.css index 8a686c42a9..1b658bccb0 100644 --- a/packages/muya/src/ui/paragraphQuickInsertMenu/index.css +++ b/packages/muya/src/ui/paragraphQuickInsertMenu/index.css @@ -39,7 +39,11 @@ background-color: var(--float-hover-color); } -.mu-quick-insert:hover .active { +.mu-quick-insert:not(.mu-keyboard-navigation):hover .active:not(:hover) { + background: transparent; +} + +.mu-quick-insert.mu-keyboard-navigation div.item:hover:not(.active) { background: transparent; } diff --git a/packages/muya/src/ui/paragraphQuickInsertMenu/index.ts b/packages/muya/src/ui/paragraphQuickInsertMenu/index.ts index dd3829ebcd..d9749d93e0 100644 --- a/packages/muya/src/ui/paragraphQuickInsertMenu/index.ts +++ b/packages/muya/src/ui/paragraphQuickInsertMenu/index.ts @@ -112,6 +112,31 @@ export class ParagraphQuickInsertMenu extends BaseScrollFloat { }; eventCenter.attachDOMEvent(domNode, 'keydown', handleKeydown); + eventCenter.attachDOMEvent(this.container!, 'mousemove', (event: Event) => { + this.container!.classList.remove('mu-keyboard-navigation'); + if (!(event.target instanceof Element)) + return; + + const itemElement = event.target.closest('.item'); + if (!itemElement || !this.container!.contains(itemElement)) + return; + + const item = this.renderArray.find(({ label }) => { + return label === itemElement.dataset.label; + }); + if (item && item !== this.activeItem) + this.activeItem = item; + }); + } + + override step(direction: 'previous' | 'next') { + this.container!.classList.add('mu-keyboard-navigation'); + super.step(direction); + } + + override hide() { + this.container!.classList.remove('mu-keyboard-navigation'); + super.hide(); } render() {