diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000000..32614a491d --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,9 @@ +preserve_hierarchy: true +commit_message: "[skip ci] Translations from Crowdin" +append_commit_message: false + +files: +- source: /i18n/en.json + translation: /i18n/%locale%.json +- source: /packages/**/i18n/en.json + translation: /packages/**/i18n/%locale%.json diff --git a/i18n/en.json b/i18n/en.json new file mode 100644 index 0000000000..b00e13704c --- /dev/null +++ b/i18n/en.json @@ -0,0 +1,169 @@ +{ + "menu": { + "pulsar": { + "about": "About Pulsar", + "view-license": "View License", + "version": "Version VERSION", + "restart-and-install-update": "Restart and Install Update", + "check-for-update": "Check for Update", + "checking-for-update": "Checking for Update", + "downloading-update": "Downloading Update", + "preferences": "Preferences", + "config": "Config", + "init-script": "Init Script", + "keymap": "Keymap", + "snippets": "Snippets", + "stylesheet": "Stylesheet", + "install-shell-commands": "Install Shell Commands", + "quit": "Quit Pulsar" + }, + "macos": { + "services": "Services", + "hide-self": "Hide Pulsar", + "hide-others": "Hide Others", + "show-all": "Show All" + }, + "file": { + "self": "File", + "new-window": "New Window", + "new-file": "New File", + "open": "Open...", + "open-file": "Open File...", + "open-folder": "Open Folder...", + "add-project-folder": "Add Project Folder", + "project-history": { + "reopen-project": "Reopen Project", + "clear": "Clear Project History" + }, + "reopen-last-item": "Reopen Last Item", + "save": "Save", + "save-as": "Save As...", + "save-all": "Save All", + "close-tab": "Close Tab", + "close-pane": "Close Pane", + "close-window": "Close Window" + }, + "edit": { + "self": "Edit", + "undo": "Undo", + "redo": "Redo", + "cut": "Cut", + "copy": "Copy", + "copy-path": "Copy Path", + "paste": "Paste", + "paste-without-reformatting": "Paste Without Reformatting", + "select-all": "Select All", + "toggle-comments": "Toggle Comments", + "lines": { + "self": "Lines", + "indent": "Indent", + "outdent": "Outdent", + "auto-indent": "Auto Indent", + "move-up": "Move Line Up", + "move-down": "Move Line Down", + "duplicate": "Duplicate Lines", + "delete": "Delete Line", + "join": "Join Lines" + }, + "columns": { + "self": "Columns", + "move-selection-left": "Move Selection Left", + "move-selection-right": "Move Selection Right" + }, + "text": { + "self": "Text", + "upper-case": "Upper Case", + "lower-case": "Lower Case", + "delete-to-end-of-word": "Delete to End of Word", + "delete-to-previous-word-boundary": "Delete to Previous Word Boundary", + "delete-to-next-word-boundary": "Delete to Next Word Boundary", + "delete-line": "Delete Line", + "transpose": "Transpose" + }, + "folding": { + "self": "Folding", + "fold": "Fold", + "unfold": "Unfold", + "fold-all": "Fold All", + "unfold-all": "Unfold All", + "fold-level-1": "Fold Level 1", + "fold-level-2": "Fold Level 2", + "fold-level-3": "Fold Level 3", + "fold-level-4": "Fold Level 4", + "fold-level-5": "Fold Level 5", + "fold-level-6": "Fold Level 6", + "fold-level-7": "Fold Level 7", + "fold-level-8": "Fold Level 8", + "fold-level-9": "Fold Level 9" + } + }, + "view": { + "self": "View", + "toggle-full-screen": "Toggle Full Screen", + "toggle-menu-bar": "Toggle Menu Bar", + "panes": { + "self": "Panes", + "split-up": "Split Up", + "split-down": "Split Down", + "split-left": "Split Left", + "split-right": "Split Right", + "focus-next": "Focus Next Pane", + "focus-previous": "Focus Previous Pane", + "focus-above": "Focus Pane Above", + "focus-below": "Focus Pane Below", + "focus-on-left": "Focus Pane On Left", + "focus-on-right": "Focus Pane On Right", + "close": "Close Pane" + }, + "developer": { + "self": "Developer", + "open-in-dev-mode": "Open In Dev Mode", + "reload-window": "Reload Window", + "run-package-specs": "Run Package Specs", + "toggle-dev-tools": "Toggle Developer Tools" + }, + "increase-font-size": "Increase Font Size", + "decrease-font-size": "Decrease Font Size", + "reset-font-size": "Reset Font Size", + "toggle-soft-wrap": "Toggle Soft Wrap" + }, + "selection": { + "self": "Selection", + "add-above": "Add Selection Above", + "add-below": "Add Selection Below", + "single": "Single Selection", + "split-into-lines": "Split into Lines", + "to-top": "Select to Top", + "to-bottom": "Select to Bottom", + "line": "Select Line", + "word": "Select Word", + "to-beginning-of-word": "Select to Beginning of Word", + "to-beginning-of-line": "Select to Beginning of Line", + "to-first-char-of-line": "Select to First Character of Line", + "to-end-of-word": "Select to End of Word", + "to-end-of-line": "Select to End of Line" + }, + "find": { + "self": "Find" + }, + "packages": { + "self": "Packages", + "open-package-manager": "Open Package Manager" + }, + "window": { + "self": "Window", + "minimise": "Minimise", + "zoom": "Zoom", + "bring-all-to-front": "Bring All to Front" + }, + "help": { + "self": "Help", + "terms-of-use": "Terms of Use", + "docs": "Documentation", + "faq": "Frequently Asked Questions", + "community-discussions": "Community Discussions", + "report-issue": "Report Issue", + "search-issues": "Search Issues" + } + } +} diff --git a/menus/darwin.cson b/menus/darwin.cson index b65f67708b..d229ad190a 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -2,227 +2,227 @@ { label: 'Pulsar' submenu: [ - { label: 'About Pulsar', command: 'application:about' } - { label: 'View License', command: 'application:open-license' } - { label: 'VERSION', enabled: false } - { label: 'Restart and Install Update', command: 'application:install-update', visible: false} - { label: 'Check for Update', command: 'application:check-for-update', visible: false} - { label: 'Checking for Update', enabled: false, visible: false} - { label: 'Downloading Update', enabled: false, visible: false} + { localisedLabel: 'core.menu.pulsar.about', command: 'application:about' } + { localisedLabel: 'core.menu.pulsar.view-license', command: 'application:open-license' } + { localisedLabel: 'core.menu.pulsar.version', enabled: false } + { localisedLabel: 'core.menu.pulsar.restart-and-install-update', command: 'application:install-update', visible: false } + { localisedLabel: 'core.menu.pulsar.check-for-update', command: 'application:check-for-update', visible: false } + { localisedLabel: 'core.menu.pulsar.checking-for-update', enabled: false, visible: false } + { localisedLabel: 'core.menu.pulsar.downloading-update', enabled: false, visible: false } { type: 'separator' } - { label: 'Preferences…', command: 'application:show-settings' } + { localisedLabel: 'core.menu.pulsar.preferences', command: 'application:show-settings' } { type: 'separator' } - { label: 'Config…', command: 'application:open-your-config' } - { label: 'Init Script…', command: 'application:open-your-init-script' } - { label: 'Keymap…', command: 'application:open-your-keymap' } - { label: 'Snippets…', command: 'application:open-your-snippets' } - { label: 'Stylesheet…', command: 'application:open-your-stylesheet' } + { localisedLabel: 'core.menu.pulsar.config', command: 'application:open-your-config' } + { localisedLabel: 'core.menu.pulsar.init-script', command: 'application:open-your-init-script' } + { localisedLabel: 'core.menu.pulsar.keymap', command: 'application:open-your-keymap' } + { localisedLabel: 'core.menu.pulsar.snippets', command: 'application:open-your-snippets' } + { localisedLabel: 'core.menu.pulsar.stylesheet', command: 'application:open-your-stylesheet' } { type: 'separator' } - { label: 'Install Shell Commands', command: 'window:install-shell-commands' } + { localisedLabel: 'core.menu.pulsar.install-shell-commands', command: 'window:install-shell-commands' } { type: 'separator' } - { label: 'Services', role: 'services', submenu: [] } + { localisedLabel: 'core.menu.macos.services', role: 'services', submenu: [] } { type: 'separator' } - { label: 'Hide Pulsar', command: 'application:hide' } - { label: 'Hide Others', command: 'application:hide-other-applications' } - { label: 'Show All', command: 'application:unhide-all-applications' } + { localisedLabel: 'core.menu.macos.hide-self', command: 'application:hide' } + { localisedLabel: 'core.menu.macos.hide-others', command: 'application:hide-other-applications' } + { localisedLabel: 'core.menu.macos.show-all', command: 'application:unhide-all-applications' } { type: 'separator' } - { label: 'Quit Pulsar', command: 'application:quit' } + { localisedLabel: 'core.menu.pulsar.quit', command: 'application:quit' } ] } { - label: 'File' + localisedLabel: 'core.menu.file.self' submenu: [ - { label: 'New Window', command: 'application:new-window' } - { label: 'New File', command: 'application:new-file' } - { label: 'Open…', command: 'application:open' } - { label: 'Add Project Folder…', command: 'application:add-project-folder' } + { localisedLabel: 'core.menu.file.new-window', command: 'application:new-window' } + { localisedLabel: 'core.menu.file.new-file', command: 'application:new-file' } + { localisedLabel: 'core.menu.file.open', command: 'application:open' } + { localisedLabel: 'core.menu.file.add-project-folder', command: 'application:add-project-folder' } { - label: 'Reopen Project', + localisedLabel: 'core.menu.file.project-history.reopen-project' submenu: [ - { label: 'Clear Project History', command: 'application:clear-project-history' } + { localisedLabel: 'core.menu.file.project-history.clear', command: 'application:clear-project-history' } { type: 'separator' } ] } - { label: 'Reopen Last Item', command: 'pane:reopen-closed-item' } + { localisedLabel: 'core.menu.file.reopen-last-item', command: 'pane:reopen-closed-item' } { type: 'separator' } - { label: 'Save', command: 'core:save' } - { label: 'Save As…', command: 'core:save-as' } - { label: 'Save All', command: 'window:save-all' } + { localisedLabel: 'core.menu.file.save', command: 'core:save' } + { localisedLabel: 'core.menu.file.save-as', command: 'core:save-as' } + { localisedLabel: 'core.menu.file.save-all', command: 'window:save-all' } { type: 'separator' } - { label: 'Close Tab', command: 'core:close' } - { label: 'Close Pane', command: 'pane:close' } - { label: 'Close Window', command: 'window:close' } + { localisedLabel: 'core.menu.file.close-tab', command: 'core:close' } + { localisedLabel: 'core.menu.file.close-pane', command: 'pane:close' } + { localisedLabel: 'core.menu.file.close-window', command: 'window:close' } ] } { - label: 'Edit' + localisedLabel: 'core.menu.edit.self' submenu: [ - { label: 'Undo', command: 'core:undo' } - { label: 'Redo', command: 'core:redo' } + { localisedLabel: 'core.menu.edit.undo', command: 'core:undo' } + { localisedLabel: 'core.menu.edit.redo', command: 'core:redo' } { type: 'separator' } - { label: 'Cut', command: 'core:cut' } - { label: 'Copy', command: 'core:copy' } - { label: 'Copy Path', command: 'editor:copy-path' } - { label: 'Paste', command: 'core:paste' } - { label: 'Paste Without Reformatting', command: 'editor:paste-without-reformatting' } - { label: 'Select All', command: 'core:select-all' } + { localisedLabel: 'core.menu.edit.cut', command: 'core:cut' } + { localisedLabel: 'core.menu.edit.copy', command: 'core:copy' } + { localisedLabel: 'core.menu.edit.copy-path', command: 'editor:copy-path' } + { localisedLabel: 'core.menu.edit.paste', command: 'core:paste' } + { localisedLabel: 'core.menu.edit.paste-without-reformatting', command: 'editor:paste-without-reformatting' } + { localisedLabel: 'core.menu.edit.select-all', command: 'core:select-all' } { type: 'separator' } - { label: 'Toggle Comments', command: 'editor:toggle-line-comments' } + { localisedLabel: 'core.menu.edit.toggle-comments', command: 'editor:toggle-line-comments' } { - label: 'Lines', + localisedLabel: 'core.menu.edit.lines.self', submenu: [ - { label: 'Indent', command: 'editor:indent-selected-rows' } - { label: 'Outdent', command: 'editor:outdent-selected-rows' } - { label: 'Auto Indent', command: 'editor:auto-indent' } + { localisedLabel: 'core.menu.edit.lines.indent', command: 'editor:indent-selected-rows' } + { localisedLabel: 'core.menu.edit.lines.outdent', command: 'editor:outdent-selected-rows' } + { localisedLabel: 'core.menu.edit.lines.auto-indent', command: 'editor:auto-indent' } { type: 'separator' } - { label: 'Move Line Up', command: 'editor:move-line-up' } - { label: 'Move Line Down', command: 'editor:move-line-down' } - { label: 'Duplicate Lines', command: 'editor:duplicate-lines' } - { label: 'Delete Line', command: 'editor:delete-line' } - { label: 'Join Lines', command: 'editor:join-lines' } + { localisedLabel: 'core.menu.edit.lines.move-up', command: 'editor:move-line-up' } + { localisedLabel: 'core.menu.edit.lines.move-down', command: 'editor:move-line-down' } + { localisedLabel: 'core.menu.edit.lines.duplicate', command: 'editor:duplicate-lines' } + { localisedLabel: 'core.menu.edit.lines.delete', command: 'editor:delete-line' } + { localisedLabel: 'core.menu.edit.lines.join', command: 'editor:join-lines' } ] } { - label: 'Columns', + localisedLabel: 'core.menu.edit.columns.self' submenu: [ - { label: 'Move Selection Left', command: 'editor:move-selection-left' } - { label: 'Move Selection Right', command: 'editor:move-selection-right' } + { localisedLabel: 'core.menu.edit.columns.move-selection-left', command: 'editor:move-selection-left' } + { localisedLabel: 'core.menu.edit.columns.move-selection-right', command: 'editor:move-selection-right' } ] } { - label: 'Text', + localisedLabel: 'core.menu.edit.text.self' submenu: [ - { label: 'Upper Case', command: 'editor:upper-case' } - { label: 'Lower Case', command: 'editor:lower-case' } + { localisedLabel: 'core.menu.edit.text.upper-case', command: 'editor:upper-case' } + { localisedLabel: 'core.menu.edit.text.lower-case', command: 'editor:lower-case' } { type: 'separator' } - { label: 'Delete to End of Word', command: 'editor:delete-to-end-of-word' } - { label: 'Delete to Previous Word Boundary', command: 'editor:delete-to-previous-word-boundary' } - { label: 'Delete to Next Word Boundary', command: 'editor:delete-to-next-word-boundary' } - { label: 'Delete Line', command: 'editor:delete-line' } + { localisedLabel: 'core.menu.edit.text.delete-to-end-of-word', command: 'editor:delete-to-end-of-word' } + { localisedLabel: 'core.menu.edit.text.delete-to-previous-word-boundary', command: 'editor:delete-to-previous-word-boundary' } + { localisedLabel: 'core.menu.edit.text.delete-to-next-word-boundary', command: 'editor:delete-to-next-word-boundary' } + { localisedLabel: 'core.menu.edit.text.delete-line', command: 'editor:delete-line' } { type: 'separator' } - { label: 'Transpose', command: 'editor:transpose' } + { localisedLabel: 'core.menu.edit.text.transpose', command: 'editor:transpose' } ] } { - label: 'Folding', + localisedLabel: 'core.menu.edit.folding.self' submenu: [ - { label: 'Fold', command: 'editor:fold-current-row' } - { label: 'Unfold', command: 'editor:unfold-current-row' } - { label: 'Fold All', command: 'editor:fold-all' } - { label: 'Unfold All', command: 'editor:unfold-all' } + { localisedLabel: 'core.menu.edit.folding.fold', command: 'editor:fold-current-row' } + { localisedLabel: 'core.menu.edit.folding.unfold', command: 'editor:unfold-current-row' } + { localisedLabel: 'core.menu.edit.folding.fold-all', command: 'editor:fold-all' } + { localisedLabel: 'core.menu.edit.folding.unfold-all', command: 'editor:unfold-all' } { type: 'separator' } - { label: 'Fold Level 1', command: 'editor:fold-at-indent-level-1' } - { label: 'Fold Level 2', command: 'editor:fold-at-indent-level-2' } - { label: 'Fold Level 3', command: 'editor:fold-at-indent-level-3' } - { label: 'Fold Level 4', command: 'editor:fold-at-indent-level-4' } - { label: 'Fold Level 5', command: 'editor:fold-at-indent-level-5' } - { label: 'Fold Level 6', command: 'editor:fold-at-indent-level-6' } - { label: 'Fold Level 7', command: 'editor:fold-at-indent-level-7' } - { label: 'Fold Level 8', command: 'editor:fold-at-indent-level-8' } - { label: 'Fold Level 9', command: 'editor:fold-at-indent-level-9' } + { localisedLabel: 'core.menu.edit.folding.fold-level-1', command: 'editor:fold-at-indent-level-1' } + { localisedLabel: 'core.menu.edit.folding.fold-level-2', command: 'editor:fold-at-indent-level-2' } + { localisedLabel: 'core.menu.edit.folding.fold-level-3', command: 'editor:fold-at-indent-level-3' } + { localisedLabel: 'core.menu.edit.folding.fold-level-4', command: 'editor:fold-at-indent-level-4' } + { localisedLabel: 'core.menu.edit.folding.fold-level-5', command: 'editor:fold-at-indent-level-5' } + { localisedLabel: 'core.menu.edit.folding.fold-level-6', command: 'editor:fold-at-indent-level-6' } + { localisedLabel: 'core.menu.edit.folding.fold-level-7', command: 'editor:fold-at-indent-level-7' } + { localisedLabel: 'core.menu.edit.folding.fold-level-8', command: 'editor:fold-at-indent-level-8' } + { localisedLabel: 'core.menu.edit.folding.fold-level-9', command: 'editor:fold-at-indent-level-9' } ] } ] } { - label: 'View' + localisedLabel: 'core.menu.view.self' submenu: [ - { label: 'Toggle Full Screen', command: 'window:toggle-full-screen' } + { localisedLabel: 'core.menu.view.toggle-full-screen', command: 'window:toggle-full-screen' } { - label: 'Panes' + localisedLabel: 'core.menu.view.panes.self' submenu: [ - { label: 'Split Up', command: 'pane:split-up-and-copy-active-item' } - { label: 'Split Down', command: 'pane:split-down-and-copy-active-item' } - { label: 'Split Left', command: 'pane:split-left-and-copy-active-item' } - { label: 'Split Right', command: 'pane:split-right-and-copy-active-item' } + { localisedLabel: 'core.menu.view.panes.split-up', command: 'pane:split-up-and-copy-active-item' } + { localisedLabel: 'core.menu.view.panes.split-down', command: 'pane:split-down-and-copy-active-item' } + { localisedLabel: 'core.menu.view.panes.split-left', command: 'pane:split-left-and-copy-active-item' } + { localisedLabel: 'core.menu.view.panes.split-right', command: 'pane:split-right-and-copy-active-item' } { type: 'separator' } - { label: 'Focus Next Pane', command: 'window:focus-next-pane' } - { label: 'Focus Previous Pane', command: 'window:focus-previous-pane' } + { localisedLabel: 'core.menu.view.panes.focus-next', command: 'window:focus-next-pane' } + { localisedLabel: 'core.menu.view.panes.focus-previous', command: 'window:focus-previous-pane' } { type: 'separator' } - { label: 'Focus Pane Above', command: 'window:focus-pane-above' } - { label: 'Focus Pane Below', command: 'window:focus-pane-below' } - { label: 'Focus Pane On Left', command: 'window:focus-pane-on-left' } - { label: 'Focus Pane On Right', command: 'window:focus-pane-on-right' } + { localisedLabel: 'core.menu.view.panes.focus-above', command: 'window:focus-pane-above' } + { localisedLabel: 'core.menu.view.panes.focus-below', command: 'window:focus-pane-below' } + { localisedLabel: 'core.menu.view.panes.focus-on-left', command: 'window:focus-pane-on-left' } + { localisedLabel: 'core.menu.view.panes.focus-on-right', command: 'window:focus-pane-on-right' } { type: 'separator' } - { label: 'Close Pane', command: 'pane:close' } + { localisedLabel: 'core.menu.view.panes.close', command: 'pane:close' } ] } { - label: 'Developer' + localisedLabel: 'core.menu.view.developer.self' submenu: [ - { label: 'Open In Dev Mode…', command: 'application:open-dev' } - { label: 'Reload Window', command: 'window:reload' } - { label: 'Run Package Specs', command: 'window:run-package-specs' } - { label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' } + { localisedLabel: 'core.menu.view.developer.open-in-dev-mode', command: 'application:open-dev' } + { localisedLabel: 'core.menu.view.developer.reload-window', command: 'window:reload' } + { localisedLabel: 'core.menu.view.developer.run-package-specs', command: 'window:run-package-specs' } + { localisedLabel: 'core.menu.view.developer.toggle-dev-tools', command: 'window:toggle-dev-tools' } ] } { type: 'separator' } - { label: 'Increase Font Size', command: 'window:increase-font-size' } - { label: 'Decrease Font Size', command: 'window:decrease-font-size' } - { label: 'Reset Font Size', command: 'window:reset-font-size' } + { localisedLabel: 'core.menu.view.increase-font-size', command: 'window:increase-font-size' } + { localisedLabel: 'core.menu.view.decrease-font-size', command: 'window:decrease-font-size' } + { localisedLabel: 'core.menu.view.reset-font-size', command: 'window:reset-font-size' } { type: 'separator' } - { label: 'Toggle Soft Wrap', command: 'editor:toggle-soft-wrap' } + { localisedLabel: 'core.menu.view.toggle-soft-wrap', command: 'editor:toggle-soft-wrap' } ] } { - label: 'Selection' + localisedLabel: 'core.menu.selection.self' submenu: [ - { label: 'Add Selection Above', command: 'editor:add-selection-above' } - { label: 'Add Selection Below', command: 'editor:add-selection-below' } - { label: 'Single Selection', command: 'editor:consolidate-selections'} - { label: 'Split into Lines', command: 'editor:split-selections-into-lines'} + { localisedLabel: 'core.menu.selection.add-above', command: 'editor:add-selection-above' } + { localisedLabel: 'core.menu.selection.add-below', command: 'editor:add-selection-below' } + { localisedLabel: 'core.menu.selection.single', command: 'editor:consolidate-selections' } + { localisedLabel: 'core.menu.selection.split-into-lines', command: 'editor:split-selections-into-lines' } { type: 'separator' } - { label: 'Select to Top', command: 'core:select-to-top' } - { label: 'Select to Bottom', command: 'core:select-to-bottom' } + { localisedLabel: 'core.menu.selection.to-top', command: 'core:select-to-top' } + { localisedLabel: 'core.menu.selection.to-bottom', command: 'core:select-to-bottom' } { type: 'separator' } - { label: 'Select Line', command: 'editor:select-line' } - { label: 'Select Word', command: 'editor:select-word' } - { label: 'Select to Beginning of Word', command: 'editor:select-to-beginning-of-word' } - { label: 'Select to Beginning of Line', command: 'editor:select-to-beginning-of-line' } - { label: 'Select to First Character of Line', command: 'editor:select-to-first-character-of-line' } - { label: 'Select to End of Word', command: 'editor:select-to-end-of-word' } - { label: 'Select to End of Line', command: 'editor:select-to-end-of-line' } + { localisedLabel: 'core.menu.selection.line', command: 'editor:select-line' } + { localisedLabel: 'core.menu.selection.word', command: 'editor:select-word' } + { localisedLabel: 'core.menu.selection.to-beginning-of-word', command: 'editor:select-to-beginning-of-word' } + { localisedLabel: 'core.menu.selection.to-beginning-of-line', command: 'editor:select-to-beginning-of-line' } + { localisedLabel: 'core.menu.selection.to-first-char-of-line', command: 'editor:select-to-first-character-of-line' } + { localisedLabel: 'core.menu.selection.to-end-of-word', command: 'editor:select-to-end-of-word' } + { localisedLabel: 'core.menu.selection.to-end-of-line', command: 'editor:select-to-end-of-line' } ] } { - label: 'Find' + localisedLabel: 'core.menu.find.self' submenu: [] } { - label: 'Packages' + localisedLabel: 'core.menu.packages.self' submenu: [ - { label: 'Open Package Manager', command: 'settings-view:view-installed-packages' } + { localisedLabel: 'core.menu.packages.open-package-manager', command: 'settings-view:view-installed-packages' } { type: 'separator' } ] } { - label: 'Window' + localisedLabel: 'core.menu.window.self' role: 'window' submenu: [ - { label: 'Minimize', command: 'application:minimize' } - { label: 'Zoom', command: 'application:zoom' } + { localisedLabel: 'core.menu.window.minimise', command: 'application:minimize' } + { localisedLabel: 'core.menu.window.zoom', command: 'application:zoom' } { type: 'separator' } - { label: 'Bring All to Front', command: 'application:bring-all-windows-to-front' } + { localisedLabel: 'core.menu.window.bring-all-to-front', command: 'application:bring-all-windows-to-front' } ] } { - label: 'Help' + localisedLabel: 'core.menu.help.self' role: 'help' submenu: [ - { label: 'Terms of Use', command: 'application:open-terms-of-use' } - { label: 'Documentation', command: 'application:open-documentation' } - { label: 'Frequently Asked Questions', command: 'application:open-faq' } + { localisedLabel: 'core.menu.help.terms-of-use', command: 'application:open-terms-of-use' } + { localisedLabel: 'core.menu.help.docs', command: 'application:open-documentation' } + { localisedLabel: 'core.menu.help.faq', command: 'application:open-faq' } { type: 'separator' } - { label: 'Community Discussions', command: 'application:open-discussions' } - { label: 'Report Issue', command: 'application:report-issue' } - { label: 'Search Issues', command: 'application:search-issues' } + { localisedLabel: 'core.menu.help.community-discussions', command: 'application:open-discussions' } + { localisedLabel: 'core.menu.help.report-issue', command: 'application:report-issue' } + { localisedLabel: 'core.menu.help.search-issues', command: 'application:search-issues' } { type: 'separator' } ] } diff --git a/menus/linux.cson b/menus/linux.cson index e4f6a0532c..66ab1b1adc 100644 --- a/menus/linux.cson +++ b/menus/linux.cson @@ -1,204 +1,204 @@ 'menu': [ { - label: '&File' + localisedLabel: 'core.menu.file.self' # accelerator F submenu: [ - { label: 'New &Window', command: 'application:new-window' } - { label: '&New File', command: 'application:new-file' } - { label: '&Open File…', command: 'application:open-file' } - { label: 'Open Folder…', command: 'application:open-folder' } - { label: 'Add Project Folder…', command: 'application:add-project-folder' } + { localisedLabel: 'core.menu.file.new-window', command: 'application:new-window' } # accelerator W + { localisedLabel: 'core.menu.file.new-file', command: 'application:new-file' } # accelerator N + { localisedLabel: 'core.menu.file.open-file', command: 'application:open-file' } # accelerator O + { localisedLabel: 'core.menu.file.open-folder', command: 'application:open-folder' } + { localisedLabel: 'core.menu.file.add-project-folder', command: 'application:add-project-folder' } { - label: 'Reopen Project', + localisedLabel: 'core.menu.file.project-history.reopen-project' submenu: [ - { label: 'Clear Project History', command: 'application:clear-project-history' } + { localisedLabel: 'core.menu.file.project-history.clear', command: 'application:clear-project-history' } { type: 'separator' } ] } - { label: 'Reopen Last &Item', command: 'pane:reopen-closed-item' } + { localisedLabel: 'core.menu.file.reopen-last-item', command: 'pane:reopen-closed-item' } # accelerator I { type: 'separator' } - { label: '&Save', command: 'core:save' } - { label: 'Save &As…', command: 'core:save-as' } - { label: 'Save A&ll', command: 'window:save-all' } + { localisedLabel: 'core.menu.file.save', command: 'core:save' } # accelerator S + { localisedLabel: 'core.menu.file.save-as', command: 'core:save-as' } # accelerator A + { localisedLabel: 'core.menu.file.save-all', command: 'window:save-all' } # accelerator L { type: 'separator' } - { label: '&Close Tab', command: 'core:close' } - { label: 'Close &Pane', command: 'pane:close' } - { label: 'Clos&e Window', command: 'window:close' } + { localisedLabel: 'core.menu.file.close-tab', command: 'core:close' } # accelerator C + { localisedLabel: 'core.menu.file.close-pane', command: 'pane:close' } # accelerator P + { localisedLabel: 'core.menu.file.close-window', command: 'window:close' } # accelerator E { type: 'separator' } - { label: 'Quit', command: 'application:quit' } + { localisedLabel: 'core.menu.pulsar.quit', command: 'application:quit' } ] } { - label: '&Edit' + localisedLabel: 'core.menu.edit.self' # accelerator E submenu: [ - { label: '&Undo', command: 'core:undo' } - { label: '&Redo', command: 'core:redo' } + { localisedLabel: 'core.menu.edit.undo', command: 'core:undo' } # accelerator U + { localisedLabel: 'core.menu.edit.redo', command: 'core:redo' } # accelerator R { type: 'separator' } - { label: '&Cut', command: 'core:cut' } - { label: 'C&opy', command: 'core:copy' } - { label: 'Copy Pat&h', command: 'editor:copy-path' } - { label: '&Paste', command: 'core:paste' } - { label: 'Paste Without Reformatting', command: 'editor:paste-without-reformatting' } - { label: 'Select &All', command: 'core:select-all' } + { localisedLabel: 'core.menu.edit.cut', command: 'core:cut' } # accelerator C + { localisedLabel: 'core.menu.edit.copy', command: 'core:copy' } # accelerator O + { localisedLabel: 'core.menu.edit.copy-path', command: 'editor:copy-path' } # accelerator H + { localisedLabel: 'core.menu.edit.paste', command: 'core:paste' } # accelerator P + { localisedLabel: 'core.menu.edit.paste-without-reformatting', command: 'editor:paste-without-reformatting' } + { localisedLabel: 'core.menu.edit.select-all', command: 'core:select-all' } # accelerator A { type: 'separator' } - { label: '&Toggle Comments', command: 'editor:toggle-line-comments' } + { localisedLabel: 'core.menu.edit.toggle-comments', command: 'editor:toggle-line-comments' } # accelerator T { - label: 'Lines', + localisedLabel: 'core.menu.edit.lines.self' submenu: [ - { label: '&Indent', command: 'editor:indent-selected-rows' } - { label: '&Outdent', command: 'editor:outdent-selected-rows' } - { label: '&Auto Indent', command: 'editor:auto-indent' } + { localisedLabel: 'core.menu.edit.lines.indent', command: 'editor:indent-selected-rows' } # accelerator I + { localisedLabel: 'core.menu.edit.lines.outdent', command: 'editor:outdent-selected-rows' } # accelerator O + { localisedLabel: 'core.menu.edit.lines.auto-indent', command: 'editor:auto-indent' } # accelerator A { type: 'separator' } - { label: 'Move Line &Up', command: 'editor:move-line-up' } - { label: 'Move Line &Down', command: 'editor:move-line-down' } - { label: 'Du&plicate Lines', command: 'editor:duplicate-lines' } - { label: 'D&elete Line', command: 'editor:delete-line' } - { label: '&Join Lines', command: 'editor:join-lines' } + { localisedLabel: 'core.menu.edit.lines.move-up', command: 'editor:move-line-up' } # accelerator U + { localisedLabel: 'core.menu.edit.lines.move-down', command: 'editor:move-line-down' } # accelerator D + { localisedLabel: 'core.menu.edit.lines.duplicate', command: 'editor:duplicate-lines' } # accelerator P + { localisedLabel: 'core.menu.edit.lines.delete', command: 'editor:delete-line' } # accelerator E + { localisedLabel: 'core.menu.edit.lines.join', command: 'editor:join-lines' } # accelerator J ] } { - label: 'Columns', + localisedLabel: 'core.menu.edit.columns.self' submenu: [ - { label: 'Move Selection &Left', command: 'editor:move-selection-left' } - { label: 'Move Selection &Right', command: 'editor:move-selection-right' } + { localisedLabel: 'core.menu.edit.columns.move-selection-left', command: 'editor:move-selection-left' } # accelerator L + { localisedLabel: 'core.menu.edit.columns.move-selection-right', command: 'editor:move-selection-right' } # accelerator R ] } { - label: 'Text', + localisedLabel: 'core.menu.edit.text.self' submenu: [ - { label: '&Upper Case', command: 'editor:upper-case' } - { label: '&Lower Case', command: 'editor:lower-case' } + { localisedLabel: 'core.menu.edit.text.upper-case', command: 'editor:upper-case' } # accelerator U + { localisedLabel: 'core.menu.edit.text.lower-case', command: 'editor:lower-case' } # accelerator L { type: 'separator' } - { label: 'Delete to End of &Word', command: 'editor:delete-to-end-of-word' } - { label: 'Delete to Previous Word Boundary', command: 'editor:delete-to-previous-word-boundary' } - { label: 'Delete to Next Word Boundary', command: 'editor:delete-to-next-word-boundary' } - { label: '&Delete Line', command: 'editor:delete-line' } + { localisedLabel: 'core.menu.edit.text.delete-to-end-of-word', command: 'editor:delete-to-end-of-word' } # accelerator W + { localisedLabel: 'core.menu.edit.text.delete-to-previous-word-boundary', command: 'editor:delete-to-previous-word-boundary' } + { localisedLabel: 'core.menu.edit.text.delete-to-next-word-boundary', command: 'editor:delete-to-next-word-boundary' } + { localisedLabel: 'core.menu.edit.text.delete-line', command: 'editor:delete-line' } # accelerator D { type: 'separator' } - { label: '&Transpose', command: 'editor:transpose' } + { localisedLabel: 'core.menu.edit.text.transpose', command: 'editor:transpose' } # accelerator T ] } { - label: 'Folding', + localisedLabel: 'core.menu.edit.folding.self' submenu: [ - { label: '&Fold', command: 'editor:fold-current-row' } - { label: '&Unfold', command: 'editor:unfold-current-row' } - { label: 'Fol&d All', command: 'editor:fold-all' } - { label: 'Unfold &All', command: 'editor:unfold-all' } + { localisedLabel: 'core.menu.edit.folding.fold', command: 'editor:fold-current-row' } # accelerator F + { localisedLabel: 'core.menu.edit.folding.unfold', command: 'editor:unfold-current-row' } # accelerator U + { localisedLabel: 'core.menu.edit.folding.fold-all', command: 'editor:fold-all' } # accelerator D + { localisedLabel: 'core.menu.edit.folding.unfold-all', command: 'editor:unfold-all' } # accelerator A { type: 'separator' } - { label: 'Fold Level 1', command: 'editor:fold-at-indent-level-1' } - { label: 'Fold Level 2', command: 'editor:fold-at-indent-level-2' } - { label: 'Fold Level 3', command: 'editor:fold-at-indent-level-3' } - { label: 'Fold Level 4', command: 'editor:fold-at-indent-level-4' } - { label: 'Fold Level 5', command: 'editor:fold-at-indent-level-5' } - { label: 'Fold Level 6', command: 'editor:fold-at-indent-level-6' } - { label: 'Fold Level 7', command: 'editor:fold-at-indent-level-7' } - { label: 'Fold Level 8', command: 'editor:fold-at-indent-level-8' } - { label: 'Fold Level 9', command: 'editor:fold-at-indent-level-9' } + { localisedLabel: 'core.menu.edit.folding.fold-level-1', command: 'editor:fold-at-indent-level-1' } + { localisedLabel: 'core.menu.edit.folding.fold-level-2', command: 'editor:fold-at-indent-level-2' } + { localisedLabel: 'core.menu.edit.folding.fold-level-3', command: 'editor:fold-at-indent-level-3' } + { localisedLabel: 'core.menu.edit.folding.fold-level-4', command: 'editor:fold-at-indent-level-4' } + { localisedLabel: 'core.menu.edit.folding.fold-level-5', command: 'editor:fold-at-indent-level-5' } + { localisedLabel: 'core.menu.edit.folding.fold-level-6', command: 'editor:fold-at-indent-level-6' } + { localisedLabel: 'core.menu.edit.folding.fold-level-7', command: 'editor:fold-at-indent-level-7' } + { localisedLabel: 'core.menu.edit.folding.fold-level-8', command: 'editor:fold-at-indent-level-8' } + { localisedLabel: 'core.menu.edit.folding.fold-level-9', command: 'editor:fold-at-indent-level-9' } ] } { type: 'separator' } - { label: '&Preferences', command: 'application:show-settings' } + { localisedLabel: 'core.menu.pulsar.preferences', command: 'application:show-settings' } # accelerator P { type: 'separator' } - { label: 'Config…', command: 'application:open-your-config' } - { label: 'Init Script…', command: 'application:open-your-init-script' } - { label: 'Keymap…', command: 'application:open-your-keymap' } - { label: 'Snippets…', command: 'application:open-your-snippets' } - { label: 'Stylesheet…', command: 'application:open-your-stylesheet' } + { localisedLabel: 'core.menu.pulsar.config', command: 'application:open-your-config' } + { localisedLabel: 'core.menu.pulsar.init-script', command: 'application:open-your-init-script' } + { localisedLabel: 'core.menu.pulsar.keymap', command: 'application:open-your-keymap' } + { localisedLabel: 'core.menu.pulsar.snippets', command: 'application:open-your-snippets' } + { localisedLabel: 'core.menu.pulsar.stylesheet', command: 'application:open-your-stylesheet' } { type: 'separator' } ] } { - label: '&View' + localisedLabel: 'core.menu.view.self' # accelerator V submenu: [ - { label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' } - { label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' } + { localisedLabel: 'core.menu.view.toggle-full-screen', command: 'window:toggle-full-screen' } # accelerator F + { localisedLabel: 'core.menu.view.toggle-menu-bar', command: 'window:toggle-menu-bar' } { - label: 'Panes' + localisedLabel: 'core.menu.view.panes.self' submenu: [ - { label: 'Split Up', command: 'pane:split-up-and-copy-active-item' } - { label: 'Split Down', command: 'pane:split-down-and-copy-active-item' } - { label: 'Split Left', command: 'pane:split-left-and-copy-active-item' } - { label: 'Split Right', command: 'pane:split-right-and-copy-active-item' } + { localisedLabel: 'core.menu.view.panes.split-up', command: 'pane:split-up-and-copy-active-item' } + { localisedLabel: 'core.menu.view.panes.split-down', command: 'pane:split-down-and-copy-active-item' } + { localisedLabel: 'core.menu.view.panes.split-left', command: 'pane:split-left-and-copy-active-item' } + { localisedLabel: 'core.menu.view.panes.split-right', command: 'pane:split-right-and-copy-active-item' } { type: 'separator' } - { label: 'Focus Next Pane', command: 'window:focus-next-pane' } - { label: 'Focus Previous Pane', command: 'window:focus-previous-pane' } + { localisedLabel: 'core.menu.view.panes.focus-next', command: 'window:focus-next-pane' } + { localisedLabel: 'core.menu.view.panes.focus-previous', command: 'window:focus-previous-pane' } { type: 'separator' } - { label: 'Focus Pane Above', command: 'window:focus-pane-above' } - { label: 'Focus Pane Below', command: 'window:focus-pane-below' } - { label: 'Focus Pane On Left', command: 'window:focus-pane-on-left' } - { label: 'Focus Pane On Right', command: 'window:focus-pane-on-right' } + { localisedLabel: 'core.menu.view.panes.focus-above', command: 'window:focus-pane-above' } + { localisedLabel: 'core.menu.view.panes.focus-below', command: 'window:focus-pane-below' } + { localisedLabel: 'core.menu.view.panes.focus-on-left', command: 'window:focus-pane-on-left' } + { localisedLabel: 'core.menu.view.panes.focus-on-right', command: 'window:focus-pane-on-right' } { type: 'separator' } - { label: 'Close Pane', command: 'pane:close' } + { localisedLabel: 'core.menu.view.panes.close', command: 'pane:close' } ] } { - label: 'Developer' + localisedLabel: 'core.menu.view.developer.self' submenu: [ - { label: 'Open In &Dev Mode…', command: 'application:open-dev' } - { label: '&Reload Window', command: 'window:reload' } - { label: 'Run Package &Specs', command: 'window:run-package-specs' } - { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } + { localisedLabel: 'core.menu.view.developer.open-in-dev-mode', command: 'application:open-dev' } # accelerator D + { localisedLabel: 'core.menu.view.developer.reload-window', command: 'window:reload' } # accelerator R + { localisedLabel: 'core.menu.view.developer.run-package-specs', command: 'window:run-package-specs' } # accelerator S + { localisedLabel: 'core.menu.view.developer.toggle-dev-tools', command: 'window:toggle-dev-tools' } # accelerator T ] } { type: 'separator' } - { label: '&Increase Font Size', command: 'window:increase-font-size' } - { label: '&Decrease Font Size', command: 'window:decrease-font-size' } - { label: 'Re&set Font Size', command: 'window:reset-font-size' } + { localisedLabel: 'core.menu.view.increase-font-size', command: 'window:increase-font-size' } # accelerator I + { localisedLabel: 'core.menu.view.decrease-font-size', command: 'window:decrease-font-size' } # accelerator D + { localisedLabel: 'core.menu.view.reset-font-size', command: 'window:reset-font-size' } # accelerator S { type: 'separator' } - { label: 'Toggle Soft &Wrap', command: 'editor:toggle-soft-wrap' } + { localisedLabel: 'core.menu.view.toggle-soft-wrap', command: 'editor:toggle-soft-wrap' } # accelerator W ] } { - label: '&Selection' + localisedLabel: 'core.menu.selection.self' # accelerator S submenu: [ - { label: 'Add Selection &Above', command: 'editor:add-selection-above' } - { label: 'Add Selection &Below', command: 'editor:add-selection-below' } - { label: 'S&plit into Lines', command: 'editor:split-selections-into-lines'} - { label: 'Single Selection', command: 'editor:consolidate-selections'} - { type: 'separator' } - { label: 'Select to &Top', command: 'core:select-to-top' } - { label: 'Select to Botto&m', command: 'core:select-to-bottom' } - { type: 'separator' } - { label: 'Select &Line', command: 'editor:select-line' } - { label: 'Select &Word', command: 'editor:select-word' } - { label: 'Select to Beginning of W&ord', command: 'editor:select-to-beginning-of-word' } - { label: 'Select to Beginning of L&ine', command: 'editor:select-to-beginning-of-line' } - { label: 'Select to First &Character of Line', command: 'editor:select-to-first-character-of-line' } - { label: 'Select to End of Wor&d', command: 'editor:select-to-end-of-word' } - { label: 'Select to End of Lin&e', command: 'editor:select-to-end-of-line' } + { localisedLabel: 'core.menu.selection.add-above', command: 'editor:add-selection-above' } # accelerator A + { localisedLabel: 'core.menu.selection.add-below', command: 'editor:add-selection-below' } # accelerator B + { localisedLabel: 'core.menu.selection.single', command: 'editor:consolidate-selections' } + { localisedLabel: 'core.menu.selection.split-into-lines', command: 'editor:split-selections-into-lines' } # accelerator P + { type: 'separator' } + { localisedLabel: 'core.menu.selection.to-top', command: 'core:select-to-top' } # accelerator T + { localisedLabel: 'core.menu.selection.to-bottom', command: 'core:select-to-bottom' } # accelerator M + { type: 'separator' } + { localisedLabel: 'core.menu.selection.line', command: 'editor:select-line' } # accelerator L + { localisedLabel: 'core.menu.selection.word', command: 'editor:select-word' } # accelerator W + { localisedLabel: 'core.menu.selection.to-beginning-of-word', command: 'editor:select-to-beginning-of-word' } # accelerator O + { localisedLabel: 'core.menu.selection.to-beginning-of-line', command: 'editor:select-to-beginning-of-line' } # accelerator I + { localisedLabel: 'core.menu.selection.to-first-char-of-line', command: 'editor:select-to-first-character-of-line' } # accelerator C + { localisedLabel: 'core.menu.selection.to-end-of-word', command: 'editor:select-to-end-of-word' } # accelerator D + { localisedLabel: 'core.menu.selection.to-end-of-line', command: 'editor:select-to-end-of-line' } # accelerator E ] } { - label: 'F&ind' + localisedLabel: 'core.menu.find.self' # accelerator I submenu: [] } { - label: '&Packages' + localisedLabel: 'core.menu.packages.self' # accelerator P submenu: [ - { label: 'Open Package Manager', command: 'settings-view:view-installed-packages' } + { localisedLabel: 'core.menu.packages.open-package-manager', command: 'settings-view:view-installed-packages' } { type: 'separator' } ] } { - label: '&Help' + localisedLabel: 'core.menu.help.self' # accelerator H submenu: [ - { label: 'View &Terms of Use', command: 'application:open-terms-of-use' } - { label: 'View &License', command: 'application:open-license' } - { label: "VERSION", enabled: false } + { localisedLabel: 'core.menu.help.terms-of-use', command: 'application:open-terms-of-use' } # accelerator T + { localisedLabel: 'core.menu.pulsar.view-license', command: 'application:open-license' } # accelerator L + { localisedLabel: 'core.menu.pulsar.version', enabled: false } { type: 'separator' } - { label: '&Documentation', command: 'application:open-documentation' } - { label: 'Frequently Asked Questions', command: 'application:open-faq' } + { localisedLabel: 'core.menu.help.docs', command: 'application:open-documentation' } # accelerator D + { localisedLabel: 'core.menu.help.faq', command: 'application:open-faq' } { type: 'separator' } - { label: 'Community Discussions', command: 'application:open-discussions' } - { label: 'Report Issue', command: 'application:report-issue' } - { label: 'Search Issues', command: 'application:search-issues' } + { localisedLabel: 'core.menu.help.community-discussions', command: 'application:open-discussions' } + { localisedLabel: 'core.menu.help.report-issue', 'application:report-issue' } + { localisedLabel: 'core.menu.help.search-issues', command: 'application:search-issues' } { type: 'separator' } - { label: 'About Pulsar', command: 'application:about' } + { localisedLabel: 'core.menu.pulsar.about', command: 'application:about' } { type: 'separator' } ] } diff --git a/package.json b/package.json index a85921a742..243e8e3147 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "dependencies": { "@atom/source-map-support": "^0.3.4", "@babel/core": "7.18.6", + "@formatjs/icu-messageformat-parser": "^2.3.0", "about": "file:packages/about", "archive-view": "file:packages/archive-view", "async": "3.2.4", @@ -83,6 +84,7 @@ "grim": "2.0.3", "image-view": "file:packages/image-view", "incompatible-packages": "file:packages/incompatible-packages", + "intl-messageformat": "^10.3.3", "jasmine-json": "~0.0", "jasmine-reporters": "1.1.0", "jasmine-tagged": "^1.1.4", diff --git a/packages/welcome/i18n/en.json b/packages/welcome/i18n/en.json new file mode 100644 index 0000000000..40042e2015 --- /dev/null +++ b/packages/welcome/i18n/en.json @@ -0,0 +1,3 @@ +{ + "title": "Welcome" +} diff --git a/packages/welcome/lib/main.js b/packages/welcome/lib/main.js index 6778d5e835..adab0e5c9c 100644 --- a/packages/welcome/lib/main.js +++ b/packages/welcome/lib/main.js @@ -2,4 +2,6 @@ import WelcomePackage from './welcome-package'; +export const t = atom.i18n.getT("welcome"); + export default new WelcomePackage(); diff --git a/packages/welcome/lib/welcome-view.js b/packages/welcome/lib/welcome-view.js index b5c143fed4..0ba9895903 100644 --- a/packages/welcome/lib/welcome-view.js +++ b/packages/welcome/lib/welcome-view.js @@ -3,6 +3,7 @@ import etch from 'etch'; import path from 'path'; +import { t } from "./main"; export default class WelcomeView { constructor(props) { @@ -126,7 +127,7 @@ export default class WelcomeView { } getTitle() { - return 'Welcome'; + return t("title"); } isEqual(other) { diff --git a/script/electron-builder.js b/script/electron-builder.js index a88a3050df..e634cb2408 100644 --- a/script/electron-builder.js +++ b/script/electron-builder.js @@ -53,6 +53,7 @@ let options = { "package.json", "dot-atom/**/*", "exports/**/*", + "i18n/**/*", "resources/**/*", "src/**/*", "static/**/*", diff --git a/src/atom-environment.js b/src/atom-environment.js index 13682f3e76..7eb00505d0 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -46,6 +46,7 @@ const TextBuffer = require('text-buffer'); const TextEditorRegistry = require('./text-editor-registry'); const StartupTime = require('./startup-time'); const { getReleaseChannel } = require('./get-app-details.js'); +const I18n = require("./i18n"); const packagejson = require("../package.json"); const stat = util.promisify(fs.stat); @@ -125,6 +126,11 @@ class AtomEnvironment { /** @type {StyleManager} */ this.styles = new StyleManager(); + this.i18n = new I18n({ + notificationManager: this.notifications, + config: this.config + }); + /** @type {PackageManager} */ this.packages = new PackageManager({ config: this.config, @@ -135,7 +141,8 @@ class AtomEnvironment { grammarRegistry: this.grammars, deserializerManager: this.deserializers, viewRegistry: this.views, - uriHandlerRegistry: this.uriHandlerRegistry + uriHandlerRegistry: this.uriHandlerRegistry, + i18n: this.i18n }); /** @type {ThemeManager} */ @@ -149,6 +156,7 @@ class AtomEnvironment { /** @type {MenuManager} */ this.menu = new MenuManager({ + i18n: this.i18n, keymapManager: this.keymaps, packageManager: this.packages }); @@ -197,10 +205,6 @@ class AtomEnvironment { this.themes.workspace = this.workspace; - if (this.keymaps.canLoadBundledKeymapsFromMemory()) { - this.keymaps.loadBundledKeymaps(); - } - this.registerDefaultCommands(); this.registerDefaultOpeners(); this.registerDefaultDeserializers(); @@ -270,25 +274,30 @@ class AtomEnvironment { this.project.replace(projectSpecification); } + this.i18n.initialize({ + configDirPath: this.configDirPath, + packages: this.packages, + resourcePath + }); + + this.packages.initialize({ + devMode, + configDirPath: this.configDirPath, + resourcePath, + safeMode + }); + this.menu.initialize({ resourcePath }); this.contextMenu.initialize({ resourcePath, devMode }); this.keymaps.configDirPath = this.configDirPath; this.keymaps.resourcePath = resourcePath; this.keymaps.devMode = devMode; - if (!this.keymaps.canLoadBundledKeymapsFromMemory()) { - this.keymaps.loadBundledKeymaps(); - } + this.keymaps.loadBundledKeymaps(); this.commands.attach(this.window); this.styles.initialize({ configDirPath: this.configDirPath }); - this.packages.initialize({ - devMode, - configDirPath: this.configDirPath, - resourcePath, - safeMode - }); this.themes.initialize({ configDirPath: this.configDirPath, resourcePath, diff --git a/src/i18n-helpers.js b/src/i18n-helpers.js new file mode 100644 index 0000000000..8e1311253d --- /dev/null +++ b/src/i18n-helpers.js @@ -0,0 +1,154 @@ +const _ = require('underscore-plus'); +const fs = require("fs"); +const path = require("path"); +const { parse } = require("@formatjs/icu-messageformat-parser"); + +class I18nCacheHelper { + constructor({ configDirPath, i18n }) { + /** + * cachedASTs[ns][lang] = string objs + * (same shape as registeredStrings but with ASTs instead of strings) + */ + this.cachedASTs = {}; + /** @type {string} */ + this.configDirPath = configDirPath; + this.i18n = i18n; + + this.loadCaches(); + + this.debouncedCleanAndSave = _.debounce(() => { + this.cleanCaches(this.i18n.registeredStrings); + this.saveCaches(); + }, 5_000); + } + + fetchAST(ns, _path, str, lang) { + let path = [ns, lang, ..._path]; + + let ast = optionalTravelDownObjectPath( + this.cachedASTs, + path + ); + if (ast && "_AST" in ast) return ast._AST; + + ast = parse(str, { + // requiresOtherClause + }); + + let lastBit = path.pop(); + let cachePath = travelDownOrMakePath(this.cachedASTs, path); + cachePath[lastBit] = { _AST: ast }; + + this.debouncedCleanAndSave(); + + return ast; + } + + /** + * go through `this.cachedASTs`, find stuff that doesn't exist in `registeredStrings`, + * then yeet them + */ + cleanCaches(registeredStrings, cachedASTs) { + if (!cachedASTs) cachedASTs = this.cachedASTs; + + Object.entries(cachedASTs).forEach(([k, cachedValue]) => { + let registeredValue = registeredStrings[k]; + + // path doesn't exist + if (!registeredValue) { + delete cachedASTs[k]; + return; + } + + // path is an object + if (typeof registeredValue === "object") { + // cached is not AST (plain obj) (good) + if ( + typeof cachedValue === "object" + && !("_AST" in cachedValue) + ) { + this.cleanCaches(registeredValue, cachedValue); + return; + } + // cached is AST (bad) + delete cachedASTs[k]; + return; + } + + // path is a string + if (typeof registeredValue === "string") { + // cached is AST (good) + if ("_AST" in cachedValue) return; + // cached is not AST (bad) + delete cachedASTs[k]; + return; + } + }); + } + + saveCaches() { + let cachedir = path.join( + this.configDirPath, + "compile-cache", + "i18n" + ); + fs.mkdirSync(cachedir, { recursive: true }); + + let cachefile = path.join(cachedir, "strings.json"); + fs.writeFileSync(cachefile, JSON.stringify(this.cachedASTs)); + } + + loadCaches() { + let cachefile = path.join( + this.configDirPath, + "compile-cache", + "i18n", + "strings.json" + ); + if (fs.existsSync(cachefile)) { + this.cachedASTs = JSON.parse(fs.readFileSync(cachefile, "utf-8")); + } + } +} + +function walkStrings(strings, cb, accum = []) { + Object.entries(strings).forEach(([k, v]) => { + let path = [...accum, k]; + if (typeof v === "string") cb(path, v, true); + else if (typeof v === "object") { + cb(path, null, false); + walkStrings(v, cb, path); + } + }); +} + +function travelDownObjectPath(obj, path) { + for (const pathFragment of path) { + obj = obj[pathFragment]; + } + return obj; +} + +function optionalTravelDownObjectPath(obj, path) { + for (const pathFragment of path) { + obj = obj[pathFragment]; + if (!obj) return undefined; + } + return obj; +} + +function travelDownOrMakePath(obj, path) { + for (const pathFragment of path) { + if (!obj[pathFragment]) obj[pathFragment] = {}; + obj = obj[pathFragment]; + } + return obj; +} + +module.exports = { + I18nCacheHelper, + walkStrings, + travelDownObjectPath, + optionalTravelDownObjectPath, + travelDownOrMakePath +}; diff --git a/src/i18n.js b/src/i18n.js new file mode 100644 index 0000000000..b5af634b42 --- /dev/null +++ b/src/i18n.js @@ -0,0 +1,255 @@ +const { splitKeyPath } = require("key-path-helpers"); +const fs = require("fs-plus"); +const path = require("path"); +const { default: IntlMessageFormat } = require("intl-messageformat"); +const { + I18nCacheHelper, + walkStrings, + travelDownObjectPath, + optionalTravelDownObjectPath, + travelDownOrMakePath +} = require("./i18n-helpers"); + +class I18n { + constructor({ notificationManager, config }) { + this.notificationManager = notificationManager; + this.config = config; + this.initialized = false; + + /** registeredStrings[ns][lang] = string objs */ + this.registeredStrings = { core: {} }; + this.cachedFormatters = {}; + } + + initialize({ configDirPath, packages, resourcePath }) { + /** @type {string} */ + this.configDirPath = configDirPath; + this.packages = packages; + /** @type {string} */ + this.resourcePath = resourcePath; + /** @type {I18nCacheHelper} */ + this.cacheHelper = new I18nCacheHelper({ configDirPath, i18n: this }); + + const ext = ".json"; + const extlen = ext.length; + const dirpath = path.join(resourcePath, "i18n"); + const dircontents = fs.readdirSync(dirpath); + + let languageTypes = dircontents.filter(p => p.endsWith(ext)) + .map(p => p.substring(0, p.length - extlen)) + .map(p => ({ + value: p, + description: `${new Intl.DisplayNames(p, { type: "language" }).of(p)} (${p})` + })); + + this.config.setSchema("core.languageSettings", { + type: "object", + description: "These settings currently require a full restart of Pulsar to take effect.", + properties: { + primaryLanguage: { + type: "string", + order: 1, + default: "en", + enum: languageTypes + }, + fallbackLanguages: { + type: "array", + order: 2, + description: "List of fallback languages, if something can't be found in the primary language. Note; `en` is always the last fallback language, to ensure that things at least show up.", + default: [], + items: { + // Array enum is meh, if you pause for the briefest moment and you + // didn't stop at a valid enum value, the entry you just typed gets yeeted + type: "string" + } + } + } + }); + + this.updateConfigs(); + + // Preload languages (getting an obj would place it in the cache) + // TODO reassess preloading the "en" fallback when we have more progress on translations + this.getCoreLanguage(this.primaryLanguage); + this.getCoreLanguage("en"); + this.packages.onDidActivatePackage(pkg => { + this.getPkgLanguage(pkg.name, this.primaryLanguage); + this.getPkgLanguage(pkg.name, "en"); + }); + + this.packages.onDidDeactivatePackage(pkg => { + if (pkg.name in this.registeredStrings) { + delete this.registeredStrings[pkg.name]; + } + }); + + this.t = (key, opts) => { + const path = splitKeyPath(key); + const languagesToTry = [ + this.primaryLanguage, + ...this.fallbackLanguages, + "en" + ]; + + for (const lang of languagesToTry) { + const str = this.tSingleLanguage(lang, path, opts); + if (typeof str === "string") return str; + } + + // key fallback + let string_opts = opts + ? `: { ${ + Object.entries(opts) + .map(o => `"${o[0]}": "${o[1]}"`) + .join(", ") + } }` + : ""; + return `${key}${string_opts}`; + } + + this.initialized = true; + } + + updateConfigs() { + this.primaryLanguage = this.config.get("core.languageSettings.primaryLanguage"); + this.fallbackLanguages = this.config.get("core.languageSettings.fallbackLanguages"); + } + + registerStrings(packageId, strings) { + if (!(typeof this.registeredStrings[packageId] === "object")) this.registeredStrings[packageId] = {}; + + walkStrings(strings, (path, string, isString) => { + let last = path.pop(); + + let obj = travelDownObjectPath(this.registeredStrings[packageId], path); + + if (isString) { + obj[last] = string; + } else if (!obj[last]) { + obj[last] = {}; + } + }); + } + + getT(ns) { + if (!ns) return this.t; + return (key, formats) => this.t(`${ns}.${key}`, formats); + } + + /** + * attempts to translate for a single language, given a preparsed path array. + * @return undefined if the language or string cannot be found, + * and throws an error if the path isn't right. + */ + tSingleLanguage(lang, _path, opts) { + let path = [..._path]; + + const ns = path.shift(); + if (!ns) throw new Error(`key path seems invalid: [${_path.map(p => `"${p}"`).join(", ")}]`); + + const languageObj = this.getLanguageObj(ns, lang); + if (languageObj === undefined) return undefined; + + const str = optionalTravelDownObjectPath(languageObj, path); + if (typeof str === "string") { + return this.format(ns, path, str, lang, opts); + } else { + return undefined; + } + } + + /** + * gets a language object from a specified namespace + * @return undefined if it can't be found + */ + getLanguageObj(ns, lang) { + return ns === "core" + ? this.getCoreLanguage(lang) + : this.getPkgLanguage(ns, lang); + } + + /** + * gets a language for `core` + * @return undefined if it can't be found + */ + getCoreLanguage(lang) { + const loaded = this.registeredStrings.core[lang] + if (loaded !== undefined) return loaded; + + const fetched = this.fetchCoreLanguageFile(lang); + if (fetched === undefined) return undefined; + + this.registeredStrings.core[lang] = fetched; + return fetched; + } + + /** + * gets a language for a specific namespace + * @return undefined if it can't be found + */ + getPkgLanguage(ns, lang) { + const loaded = this.registeredStrings[ns]?.[lang]; + if (loaded !== undefined) return loaded; + + const fetched = this.fetchPkgLanguageFile(ns, lang); + if (fetched === undefined) return; + + if (typeof this.registeredStrings[ns] !== "object" || this.registeredStrings[ns] === null) { + this.registeredStrings[ns] = {}; + } + this.registeredStrings[ns][lang] = fetched; + } + + /** + * fetches a core language from the disk + * @return undefined if it can't be found + */ + fetchCoreLanguageFile(lang) { + let filepath = path.join(this.resourcePath, "i18n", `${lang}.json`); + let contents = JSON.parse(fs.readFileSync(filepath)); + + return contents; + } + + /** + * fetches a language for a specific namespace + * @return undefined if it can't be found + */ + fetchPkgLanguageFile(ns, lang) { + // TODO this could probably be optimised + let packages = this.packages.getAvailablePackages(); + let foundPackage = packages.find(p => p.name === ns); + + if (foundPackage === undefined) return; + + const i18nDir = path.join(foundPackage.path, "i18n"); + const langfile = path.join(i18nDir, `${lang}.json`); + + if (!(fs.isDirectorySync(i18nDir) && fs.existsSync(langfile))) return; + + let contents = JSON.parse(fs.readFileSync(langfile)); + + return contents; + } + + /** + * formats a string with opts, + * and caches the message formatter for the provided path. + */ + format(ns, _path, str, lang, opts) { + let path = [ns, lang, ..._path]; + + let cachedFormatter = optionalTravelDownObjectPath(this.cachedFormatters, path); + if (cachedFormatter !== undefined) return cachedFormatter.format(opts); + + let ast = this.cacheHelper.fetchAST(ns, _path, str, lang); + let formatter = new IntlMessageFormat(ast, lang); + + let last = path.pop(); + let cachePath = travelDownOrMakePath(this.cachedFormatters, path); + cachePath[last] = formatter; + return formatter.format(opts); + } +} + +module.exports = I18n; diff --git a/src/keymap-extensions.js b/src/keymap-extensions.js index 9fdbe2b219..45492685a6 100644 --- a/src/keymap-extensions.js +++ b/src/keymap-extensions.js @@ -4,10 +4,7 @@ const KeymapManager = require('atom-keymap'); const CSON = require('season'); const buildMetadata = require('../package.json'); -var bundledKeymaps; -if (buildMetadata != null) { - bundledKeymaps = buildMetadata._atomKeymaps; -} +var bundledKeymaps = buildMetadata._atomKeymaps; KeymapManager.prototype.onDidLoadBundledKeymaps = function(callback) { return this.emitter.on('did-load-bundled-keymaps', callback); diff --git a/src/main-process/application-menu.js b/src/main-process/application-menu.js index 1de79ea81c..ec2ba2d559 100644 --- a/src/main-process/application-menu.js +++ b/src/main-process/application-menu.js @@ -102,9 +102,10 @@ module.exports = class ApplicationMenu { // Replaces VERSION with the current version. substituteVersion(template) { let item = this.flattenMenuTemplate(template).find( - ({ label }) => label === 'VERSION' + ({ label }) => label?.includes("VERSION") ); - if (item) item.label = `Version ${this.version}`; + // TODO maybe this can be done with the i18n API instead of custom replace? + if (item) item.label = item.label.replace("VERSION", this.version); } // Default list of menu items. diff --git a/src/menu-helpers.js b/src/menu-helpers.js index b413d558f6..4980ac8d70 100644 --- a/src/menu-helpers.js +++ b/src/menu-helpers.js @@ -12,8 +12,8 @@ function addItemToMenu(item, menu) { } } -function merge(menu, item, itemSpecificity = Infinity) { - item = cloneMenuItem(item); +function merge(menu, item, t, itemSpecificity = Infinity) { + item = cloneAndLocaliseMenuItem(item, t); ItemSpecificities.set(item, itemSpecificity); const matchingItemIndex = findMatchingItemIndex(menu, item); @@ -25,7 +25,7 @@ function merge(menu, item, itemSpecificity = Infinity) { const matchingItem = menu[matchingItemIndex]; if (item.submenu != null) { for (let submenuItem of item.submenu) { - merge(matchingItem.submenu, submenuItem, itemSpecificity); + merge(matchingItem.submenu, submenuItem, t, itemSpecificity); } } else if ( itemSpecificity && @@ -35,8 +35,9 @@ function merge(menu, item, itemSpecificity = Infinity) { } } -function unmerge(menu, item) { - item = cloneMenuItem(item); + +function unmerge(menu, item, t) { + item = cloneAndLocaliseMenuItem(item, t); const matchingItemIndex = findMatchingItemIndex(menu, item); if (matchingItemIndex === -1) { return; @@ -74,11 +75,12 @@ function normalizeLabel(label) { return process.platform === 'darwin' ? label : label.replace(/&/g, ''); } -function cloneMenuItem(item) { +function cloneAndLocaliseMenuItem(item, t) { item = _.pick( item, 'type', 'label', + 'localisedLabel', 'id', 'enabled', 'visible', @@ -92,11 +94,18 @@ function cloneMenuItem(item) { 'beforeGroupContaining', 'afterGroupContaining' ); + if (item.localisedLabel) { + if (typeof item.localisedLabel === "string") { + item.label = t(item.localisedLabel); + } else { + item.label = t(item.localisedLabel.key, item.localisedLabel.opts); + } + } if (item.id === null || item.id === undefined) { item.id = normalizeLabel(item.label); } if (item.submenu != null) { - item.submenu = item.submenu.map(submenuItem => cloneMenuItem(submenuItem)); + item.submenu = item.submenu.map(submenuItem => cloneAndLocaliseMenuItem(submenuItem, t)); } return item; } @@ -133,6 +142,6 @@ module.exports = { merge, unmerge, normalizeLabel, - cloneMenuItem, + cloneAndLocaliseMenuItem, acceleratorForKeystroke }; diff --git a/src/menu-manager.js b/src/menu-manager.js index c67e338976..42311bdd3c 100644 --- a/src/menu-manager.js +++ b/src/menu-manager.js @@ -60,8 +60,8 @@ if (buildMetadata) { // // See {::add} for more info about adding menu's directly. module.exports = MenuManager = class MenuManager { - constructor({resourcePath, keymapManager, packageManager}) { - this.resourcePath = resourcePath; + constructor({ i18n, keymapManager, packageManager }) { + this.i18n = i18n; this.keymapManager = keymapManager; this.packageManager = packageManager; this.initialized = false; @@ -101,9 +101,8 @@ module.exports = MenuManager = class MenuManager { // added menu items. add(items) { items = _.deepClone(items); - for (let i = 0; i < items.length; i++) { - const item = items[i]; - if (item.label == null) { + for (const item of items) { + if (item.label == null && item.localisedLabel == null) { continue; // TODO: Should we emit a warning here? } this.merge(this.template, item); @@ -220,11 +219,11 @@ module.exports = MenuManager = class MenuManager { // Merges an item in a submenu aware way such that new items are always // appended to the bottom of existing menus where possible. merge(menu, item) { - MenuHelpers.merge(menu, item); + MenuHelpers.merge(menu, item, this.i18n.t); } unmerge(menu, item) { - MenuHelpers.unmerge(menu, item); + MenuHelpers.unmerge(menu, item, this.i18n.t); } sendToBrowserProcess(template, keystrokesByCommand) { @@ -242,7 +241,8 @@ module.exports = MenuManager = class MenuManager { } sortPackagesMenu() { - const packagesMenu = _.find(this.template, ({id}) => MenuHelpers.normalizeLabel(id) === 'Packages'); + let packagesLabel = this.i18n.t("core.menu.packages.self"); + const packagesMenu = _.find(this.template, ({id}) => MenuHelpers.normalizeLabel(id) === packagesLabel); if (!(packagesMenu && packagesMenu.submenu != null)) { return; } @@ -255,5 +255,4 @@ module.exports = MenuManager = class MenuManager { }); return this.update(); } - }; diff --git a/src/package-manager.js b/src/package-manager.js index 23a8e904dc..372276357c 100644 --- a/src/package-manager.js +++ b/src/package-manager.js @@ -38,7 +38,8 @@ module.exports = class PackageManager { grammarRegistry: this.grammarRegistry, deserializerManager: this.deserializerManager, viewRegistry: this.viewRegistry, - uriHandlerRegistry: this.uriHandlerRegistry + uriHandlerRegistry: this.uriHandlerRegistry, + i18n: this.i18n } = params); this.emitter = new Emitter(); @@ -587,7 +588,8 @@ module.exports = class PackageManager { menuManager: this.menuManager, contextMenuManager: this.contextMenuManager, deserializerManager: this.deserializerManager, - viewRegistry: this.viewRegistry + viewRegistry: this.viewRegistry, + i18n: this.i18n }; pack = metadata.theme ? new ThemePackage(options) : new Package(options); @@ -692,7 +694,8 @@ module.exports = class PackageManager { menuManager: this.menuManager, contextMenuManager: this.contextMenuManager, deserializerManager: this.deserializerManager, - viewRegistry: this.viewRegistry + viewRegistry: this.viewRegistry, + i18n: this.i18n }; const pack = metadata.theme diff --git a/src/package.js b/src/package.js index 0071472dc2..547073270c 100644 --- a/src/package.js +++ b/src/package.js @@ -30,6 +30,7 @@ module.exports = class Package { this.contextMenuManager = params.contextMenuManager; this.deserializerManager = params.deserializerManager; this.viewRegistry = params.viewRegistry; + this.i18n = params.i18n; this.emitter = new Emitter(); this.mainModule = null; @@ -240,7 +241,10 @@ module.exports = class Package { } if (typeof this.mainModule.activate === 'function') { this.mainModule.activate( - this.packageManager.getPackageState(this.name) || {} + this.packageManager.getPackageState(this.name) || {}, + { + t: this.i18n.getT(this.name) + } ); } this.mainActivated = true; diff --git a/yarn.lock b/yarn.lock index 845064f393..f110666b3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1431,6 +1431,45 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@formatjs/ecma402-abstract@1.17.0": + version "1.17.0" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.0.tgz#2ce191a3bde4c65c6684e03fa247062a4a294b9e" + integrity sha512-6ueQTeJZtwKjmh23bdkq/DMqH4l4bmfvtQH98blOSbiXv/OUiyijSW6jU22IT8BNM1ujCaEvJfTtyCYVH38EMQ== + dependencies: + "@formatjs/intl-localematcher" "0.4.0" + tslib "^2.4.0" + +"@formatjs/fast-memoize@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz#33bd616d2e486c3e8ef4e68c99648c196887802b" + integrity sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA== + dependencies: + tslib "^2.4.0" + +"@formatjs/icu-messageformat-parser@2.6.0", "@formatjs/icu-messageformat-parser@^2.3.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.6.0.tgz#b0d58ce8c8f472969c96b5cd0b3ad5522d3a02b7" + integrity sha512-yT6at0qc0DANw9qM/TU8RZaCtfDXtj4pZM/IC2WnVU80yAcliS3KVDiuUt4jSQAeFL9JS5bc2hARnFmjPdA6qw== + dependencies: + "@formatjs/ecma402-abstract" "1.17.0" + "@formatjs/icu-skeleton-parser" "1.6.0" + tslib "^2.4.0" + +"@formatjs/icu-skeleton-parser@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.6.0.tgz#0728be8b6b3656f1a4b8e6e5b0e02dffffc23c6c" + integrity sha512-eMmxNpoX/J1IPUjPGSZwo0Wh+7CEvdEMddP2Jxg1gQJXfGfht/FdW2D5XDFj3VMbOTUQlDIdZJY7uC6O6gjPoA== + dependencies: + "@formatjs/ecma402-abstract" "1.17.0" + tslib "^2.4.0" + +"@formatjs/intl-localematcher@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.4.0.tgz#63bbc37a7c3545a1bf1686072e51d9a3aed98d6b" + integrity sha512-bRTd+rKomvfdS4QDlVJ6TA/Jx1F2h/TBVO5LjvhQ7QPPHp19oPNMIum7W2CMEReq/zPxpmCeB31F9+5gl/qtvw== + dependencies: + tslib "^2.4.0" + "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -5484,6 +5523,16 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +intl-messageformat@^10.3.3: + version "10.5.0" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.0.tgz#86d11b15913ac954075b25253f5e669359f89538" + integrity sha512-AvojYuOaRb6r2veOKfTVpxH9TrmjSdc5iR9R5RgBwrDZYSmAAFVT+QLbW3C4V7Qsg0OguMp67Q/EoUkxZzXRGw== + dependencies: + "@formatjs/ecma402-abstract" "1.17.0" + "@formatjs/fast-memoize" "2.2.0" + "@formatjs/icu-messageformat-parser" "2.6.0" + tslib "^2.4.0" + invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" @@ -9492,7 +9541,7 @@ truncate-utf8-bytes@^1.0.0: dependencies: utf8-byte-length "^1.0.1" -tslib@^2.3.0: +tslib@^2.3.0, tslib@^2.4.0: version "2.6.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==