diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e0074e5..99e7d575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### ✨ New features +- Localization implemented for the whole extension + ### 🎨 Enhancements - [#273](https://github.com/estruyf/vscode-front-matter/issues/273): Allow single value arrays to be set as a string with the `singleValueAsString` field property @@ -25,6 +27,8 @@ - [#694](https://github.com/estruyf/vscode-front-matter/issues/694): Start terminal session from the folder where the `frontmatter.json` file is located - [#696](https://github.com/estruyf/vscode-front-matter/issues/696): Close the local server terminal on restart - [#699](https://github.com/estruyf/vscode-front-matter/issues/699): Changing border theme variable for the dashboard header +- [#703](https://github.com/estruyf/vscode-front-matter/issues/703): Fix retrieval of Astro Collections for `pnpm` projects +- [#704](https://github.com/estruyf/vscode-front-matter/issues/704): Fix `zod` schema script for optional fields ## [9.3.1] - 2023-10-27 diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index dc967c25..90052cfb 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -28,6 +28,13 @@ "common.pin": "Pin", "common.unpin": "Unpin", "common.noResults": "No results", + "common.error": "Sorry, something went wrong.", + "common.yes": "yes", + "common.no": "no", + "common.openSettings": "Open settings", + + "notifications.outputChannel.link": "output window", + "notifications.outputChannel.description": "Check the {0} for more details.", "settings.view.common": "Common", "settings.view.contentFolders": "Content folders", @@ -269,6 +276,7 @@ "dashboard.steps.stepsToGetStarted.showDashboard.description": "Once all actions are completed, the dashboard can be loaded.", "dashboard.steps.stepsToGetStarted.template.name": "Use a configuration template", "dashboard.steps.stepsToGetStarted.template.description": "Select a template to prefill the frontmatter.json file with the recommended settings.", + "dashboard.steps.stepsToGetStarted.template.warning": "Selecting a template applies a whole configuration to your project and closes this configuration view.", "dashboard.steps.stepsToGetStarted.astroContentTypes.name": "Create Content-Types for your Astro Content Collections", "dashboard.taxonomyView.button.add.title": "Add {0} to taxonomy settings", @@ -312,7 +320,7 @@ "dashboard.media.detailsSlideOver.unmapped.description": "Do you want to remap the metadata of unmapped files?", "dashboard.configuration.astro.astroContentTypes.empty": "No Astro Content Collections found.", - "dashboard.configuration.astro.astroContentTypes.description": "The following Astro Content Collections and can be used to generate a content-type.", + "dashboard.configuration.astro.astroContentTypes.description": "The following Astro Content Collections can be used to generate a content-type.", "panel.contentType.contentTypeValidator.title": "Content-type", "panel.contentType.contentTypeValidator.hint": "We noticed field differences between the content-type and the front matter data. \n Would you like to create, update, or set the content-type for this content?", @@ -457,5 +465,244 @@ "panel.viewPanel.mediaInsert": "Continue in the media dashboard to select the image you want to insert.", - "listeners.dashboard.settingsListener.triggerTemplate.notification": "Template files copied." + "commands.article.notification.noTaxonomy": "No {0} configured.", + "commands.article.quickPick.placeholder": "Select your {0} to insert.", + "commands.article.setDate.error": "Something failed while parsing the date format. Check your \"{0}\" setting.", + "commands.article.updateSlug.error": "Failed to rename file: {0}", + + "commands.cache.cleared": "Cache cleared", + + "commands.chatbot.title": "Ask me anything", + + "commands.content.option.contentType.label": "Create content by content type", + "commands.content.option.contentType.description": "Select if you want to create new content by the available content type(s)", + "commands.content.option.template.label": "Create content by template", + "commands.content.option.template.description": "Select if you want to create new content by the available template(s)", + "commands.content.quickPick.title": "Create content", + "commands.content.quickPick.placeholder": "Select how you want to create your new content", + + "commands.dashboard.title": "Dashboard", + + "commands.folders.addMediaFolder.inputBox.title": "Add media folder", + "commands.folders.addMediaFolder.inputBox.prompt": "Which name would you like to give to your folder (use \"/\" to create multi-level folders)?", + "commands.folders.addMediaFolder.noFolder.warning": "No folder name was specified.", + "commands.folders.create.folderExists.warning": "Folder is already registered", + "commands.folders.create.input.title": "Register folder", + "commands.folders.create.input.prompt": "Which name would you like to specify for this folder?", + "commands.folders.create.input.placeholder": "Folder name", + "commands.folders.create.success": "Folder registered", + "commands.folders.getWorkspaceFolder.workspaceFolderPick.placeholder": "Please select the main workspace folder for Front Matter to use.", + "commands.folders.get.notificationError.title": "Folder \"{0}\" does not exist. Please remove it from the settings.", + "commands.folders.get.notificationError.remove.action": "Remove folder", + "commands.folders.get.notificationError.create.action": "Create folder", + + "commands.preview.panel.title": "Preview: {0}", + "commands.preview.askUserToPickFolder.title": "Select the folder of the article to preview", + + "commands.project.initialize.success": "Project initialized successfully.", + "commands.project.switchProject.title": "To which project do you want to switch?", + "commands.project.createSampleTemplate.info": "Sample template created.", + + "commands.settings.create.input.prompt": "Insert the value of the {0} that you want to add to your configuration.", + "commands.settings.create.input.placeholder": "Name of the {0}", + "commands.settings.create.warning": "The provided {0} already exists.", + "commands.settings.create.quickPick.placeholder": "Do you want to add the new {0} to the page?", + "commands.settings.export.progress.title": "{0}: exporting tags and categories", + "commands.settings.export.progress.success": "Export completed. Tags: {0} - Categories: {1}.", + "commands.settings.remap.quickpick.title": "Remap", + "commands.settings.remap.quickpick.placeholder": "What do you want to remap?", + "commands.settings.remap.noTaxonomy.warning": "No {0} configured.", + "commands.settings.remap.selectTaxonomy.placeholder": "Select your {0} to insert.", + "commands.settings.remap.newOption.input.prompt": "Specify the value of the {0} with which you want to remap \"{1}\". Leave the input if you want to remove the {0} from all articles.", + "commands.settings.remap.newOption.input.placeholder": "Name of the {0}", + "commands.settings.remap.delete.placeholder": "Delete {0} {1}?", + + "commands.statusListener.verifyRequiredFields.diagnostic.emptyField": "The {0} field is required. Please define a value for the field.", + "commands.statusListener.verifyRequiredFields.notification.error": "The following fields are required to contain a value: {0}", + + "commands.template.generate.input.title": "Template title", + "commands.template.generate.input.prompt": "Which name would you like to give your template?", + "commands.template.generate.input.placeholder": "article", + "commands.template.generate.noTitle.warning": "You did not specify a template title.", + "commands.template.generate.keepContents.title": "Keep content", + "commands.template.generate.keepContents.placeholder": "Do you want to keep the contents for the template?", + "commands.template.generate.keepContents.noOption.warning": "You did not pick any of the options for keeping the template its content.", + "commands.template.generate.keepContents.success": "Template created and is now available in your {0} folder.", + "commands.template.getTemplates.warning": "No templates found.", + "commands.template.create.folderPath.warning": "Incorrect project folder path retrieved.", + "commands.template.create.noTemplates.warning": "No templates found.", + "commands.template.create.selectTemplate.title": "Select a template", + "commands.template.create.selectTemplate.placeholder": "Select the content template to use", + "commands.template.create.selectTemplate.noTemplate.warning": "No template selected.", + "commands.template.create.selectTemplate.notFound.warning": "Content template could not be found.", + "commands.template.create.success": "Your new content is now available.", + + "commands.wysiwyg.command.unorderedList.label": "Unordered list", + "commands.wysiwyg.command.unorderedList.detail": "Add an unordered list", + "commands.wysiwyg.command.orderedList.label": "Ordered list", + "commands.wysiwyg.command.orderedList.detail": "Add an ordered list", + "commands.wysiwyg.command.taskList.label": "Task list", + "commands.wysiwyg.command.taskList.detail": "Add a task list", + "commands.wysiwyg.command.code.label": "Code", + "commands.wysiwyg.command.code.detail": "Add inline code snippet", + "commands.wysiwyg.command.codeblock.label": "Code block", + "commands.wysiwyg.command.codeblock.detail": "Add a code block", + "commands.wysiwyg.command.blockquote.label": "Blockquote", + "commands.wysiwyg.command.blockquote.detail": "Add a blockquote", + "commands.wysiwyg.command.strikethrough.label": "Strikethrough", + "commands.wysiwyg.command.strikethrough.detail": "Add strikethrough text", + "commands.wysiwyg.quickPick.title": "WYSIWYG Options", + "commands.wysiwyg.quickPick.placeholder": "Which type of markup would you like to insert?", + "commands.wysiwyg.addHyperlink.hyperlinkInput.title": "WYSIWYG Hyperlink", + "commands.wysiwyg.addHyperlink.hyperlinkInput.prompt": "Enter the URL", + "commands.wysiwyg.addHyperlink.textInput.title": "WYSIWYG Text", + "commands.wysiwyg.addHyperlink.textInput.prompt": "Enter the text for the hyperlink", + "commands.wysiwyg.insertText.heading.input.title": "Heading level", + "commands.wysiwyg.insertText.heading.input.placeholder": "Which heading level do you want to insert?", + + "helpers.articleHelper.createContent.pageBundle.error": "A page bundle with the name {0} already exists in {1}.", + "helpers.articleHelper.createContent.contentExists.warning": "Content with the title already exists. Please specify a new title.", + "helpers.articleHelper.processCustomPlaceholders.placeholder.error": "Error while processing the {0} placeholder.", + "helpers.articleHelper.parseFile.diagnostic.error": "Error parsing the front matter of {0}.", + + "helpers.contentType.generate.noFrontMatter.error": "No front matter data found to generate a content type.", + "helpers.contentType.generate.override.quickPick.title": "Override the default content type", + "helpers.contentType.generate.override.quickPick.placeholder": "Do you want to overwrite the default content type configuration with the fields used in the current field?", + "helpers.contentType.generate.contentTypeInput.title": "Generate Content Type", + "helpers.contentType.generate.contentTypeInput.prompt": "Enter the name of the content type to generate", + "helpers.contentType.generate.contentTypeInput.validation.enterName": "Please enter a name for the content type.", + "helpers.contentType.generate.contentTypeInput.validation.nameExists": "A content type with this name already exists.", + "helpers.contentType.generate.noContentTypeName.warning": "You didn't specify a name for the content type.", + "helpers.contentType.generate.pageBundle.quickPick.title": "Use as a page bundle", + "helpers.contentType.generate.pageBundle.quickPick.placeHolder": "Do you want to use this content type as a page bundle?", + "helpers.contentType.generate.updated.success": "Content type {0} has been updated.", + "helpers.contentType.generate.generated.success": "Content type {0} has been generated.", + "helpers.contentType.addMissingFields.noFrontMatter.warning": "No front matter data found to add missing fields.", + "helpers.contentType.addMissingFields.updated.success": "Content type {0} has been updated.", + "helpers.contentType.setContentType.noFrontMatter.warning": "No front matter data found to set the content type.", + "helpers.contentType.setContentType.quickPick.title": "Select the content type", + "helpers.contentType.setContentType.quickPick.placeholder": "Which content type would you like to use?", + "helpers.contentType.create.progress.title": "{0}: Creating content...", + "helpers.contentType.create.success": "Your new content has been created.", + "helpers.contentType.verify.warning": "The content type actions are not available in this mode.", + + "helpers.customScript.executing": "Executing: {0}", + "helpers.customScript.singleRun.article.warning": "{0}: Article couldn't be retrieved.", + "helpers.customScript.bulkRun.noFiles.warning": "{0}: No files found", + "helpers.customScript.runMediaScript.noFolder.warning": "{0}: There was no folder or media path specified.", + "helpers.customScript.showOutput.frontMatter.success": "{0}: front matter updated.", + "helpers.customScript.showOutput.copyOutput.action": "Copy output", + "helpers.customScript.showOutput.success": "{0}: Executed your custom script.", + "helpers.customScript.validateCommand.error": "Invalid command: {0}", + + "helpers.dataFileHelper.process.error": "Something went wrong while processing the data file.", + + "helpers.extension.getVersion.changelog": "Check the changelog", + "helpers.extension.getVersion.starIt": "Give it a ⭐️", + "helpers.extension.getVersion.update.notification": "{0} has been updated to v{1} β€” check out what's new!", + "helpers.extension.migrateSettings.deprecated.warning": "The \"{0}\" and \"{1}\" settings have been deprecated. Please use the \"isPublishDate\" and \"isModifiedDate\" datetime field properties instead.", + "helpers.extension.migrateSettings.deprecated.warning.hide": "Hide", + "helpers.extension.migrateSettings.deprecated.warning.seeGuide": "See migration guide", + "helpers.extension.migrateSettings.templates.quickPick.title": "{0} - Templates", + "helpers.extension.migrateSettings.templates.quickPick.placeholder": "Do you want to keep on using the template functionality?", + "helpers.extension.checkIfExtensionCanRun.warning": "Front Matter BETA cannot be used while the stable version is installed. Please ensure that you have only over version installed.", + + "helpers.mediaHelper.saveFile.folder.error": "We couldn't find your selected folder.", + "helpers.mediaHelper.saveFile.file.uploaded.success": "File {0} uploaded to: {1}", + "helpers.mediaHelper.saveFile.file.uploaded.failed": "Sorry, something went wrong uploading {0}", + "helpers.mediaHelper.deleteFile.file.deletion.failed": "Sorry, something went wrong deleting {0}", + + "helpers.mediaLibrary.remove.warning": "The name \"{0}\" already exists at the file location.", + "helpers.mediaLibrary.remove.error": "Sorry, something went wrong updating \"{0}\" to \"{1}\".", + + "helpers.openFileInEditor.error": "Couldn't open the file.", + + "helpers.questions.contentTitle.aiInput.title": "Title or description", + "helpers.questions.contentTitle.aiInput.prompt": "What would you like to write about?", + "helpers.questions.contentTitle.aiInput.placeholder": "What would you like to write about?", + "helpers.questions.contentTitle.aiInput.quickPick.title.separator": "your title/description", + "helpers.questions.contentTitle.aiInput.quickPick.ai.separator": "AI generated title", + "helpers.questions.contentTitle.aiInput.select.title": "Select a title", + "helpers.questions.contentTitle.aiInput.select.placeholder": "Select a title for your content", + "helpers.questions.contentTitle.aiInput.failed": "Failed fetching the AI title. Please try to use your own title or try again later.", + "helpers.questions.contentTitle.aiInput.warning": "You did not specify a title for your content.", + "helpers.questions.contentTitle.titleInput.title": "Content title", + "helpers.questions.contentTitle.titleInput.prompt": "What would you like to use as a title for the content to create?", + "helpers.questions.contentTitle.titleInput.placeholder": "Content title", + "helpers.questions.contentTitle.titleInput.warning": "You did not specify a title for your content.", + "helpers.questions.selectContentFolder.quickPick.title": "Select a folder", + "helpers.questions.selectContentFolder.quickPick.placeholder": "Select where you want to create your content", + "helpers.questions.selectContentFolder.quickPick.noFolders.warning": "No page folders were configured.", + "helpers.questions.selectContentFolder.quickPick.noSelection.warning": "You didn't select a place where you wanted to create your content.", + "helpers.questions.selectContentType.noContentType.warning": "No content types found. Please create a content type first.", + "helpers.questions.selectContentType.quickPick.title": "Content type", + "helpers.questions.selectContentType.quickPick.placeholder": "Select the content type to create your new content", + "helpers.questions.selectContentType.noSelection.warning": "No content type was selected.", + + "helpers.seoHelper.checkLength.diagnostic.message": "Article {0} is longer than {1} characters (current length: {2}). For SEO reasons, it would be better to make it less than {1} characters.", + + "helpers.settingsHelper.checkToPromote.message": "You have local settings. Would you like to promote them to the global settings (\"frontmatter.json\")?", + "helpers.settingsHelper.promote.success": "All settings promoted to team level.", + "helpers.settingsHelper.readConfig.progress.title": "{0}: Reading dynamic config file...", + "helpers.settingsHelper.readConfig.error": "Error reading your configuration.", + "helpers.settingsHelper.refreshConfig.success": "Settings have been refreshed.", + + "helpers.taxonomyHelper.rename.input.title": "Rename the {0}", + "helpers.taxonomyHelper.rename.validate.equalValue": "The new value must be different from the old one.", + "helpers.taxonomyHelper.rename.validate.noValue": "A new value must be provided.", + "helpers.taxonomyHelper.merge.quickPick.title": "Merge the \"{0}\" with another {1} value", + "helpers.taxonomyHelper.merge.quickPick.placeholder": "Select the {0} value to merge with", + "helpers.taxonomyHelper.delete.quickPick.title": "Delete the \"{0}\" {1} value", + "helpers.taxonomyHelper.delete.quickPick.placeholder": "Are you sure you want to delete the \"{0}\" {1} value?", + "helpers.taxonomyHelper.createNew.input.title": "Create a new {0} value", + "helpers.taxonomyHelper.createNew.input.placeholder": "Enter the value you want to add", + "helpers.taxonomyHelper.createNew.input.validate.noValue": "A value must be provided.", + "helpers.taxonomyHelper.createNew.input.validate.exists": "The value already exists.", + "helpers.taxonomyHelper.process.edit": "{0}: Renaming \"{1}\" from {2} to {3}.", + "helpers.taxonomyHelper.process.merge": "{0}: Merging \"{1}\" from {2} to {3}.", + "helpers.taxonomyHelper.process.delete": "{0}: Deleting \"{1}\" from {2}.", + "helpers.taxonomyHelper.process.edit.success": "Edit completed.", + "helpers.taxonomyHelper.process.merge.success": "Merge completed.", + "helpers.taxonomyHelper.process.delete.success": "Deletion completed.", + "helpers.taxonomyHelper.move.quickPick.title": "Move the \"{0}\" to another type", + "helpers.taxonomyHelper.move.quickPick.placeholder": "Select the type to move to", + "helpers.taxonomyHelper.move.progress.title": "{0}: Moving \"{1}\" from {2} to \"${3}\".", + "helpers.taxonomyHelper.move.success": "Move completed.", + + "listeners.dashboard.dashboardListener.openConfig.notification": "Open the \"frontmatter.json\" file if you want to review the configuration.", + "listeners.dashboard.dashboardListener.pinItem.noPath.error": "No path provided.", + "listeners.dashboard.dashboardListener.pinItem.coundNotPin.error": "Could not pin item.", + "listeners.dashboard.dashboardListener.pinItem.coundNotUnPin.error": "Could not unpin item.", + + "listeners.dashboard.settingsListener.triggerTemplate.notification": "Template files copied.", + "listeners.dashboard.settingsListener.triggerTemplate.progress.title": "Downloading and initializing the template...", + "listeners.dashboard.settingsListener.triggerTemplate.download.error": "Failed to download the template.", + "listeners.dashboard.settingsListener.triggerTemplate.init.error": "Failed to initialize the template.", + + "listeners.dashboard.snippetListener.addSnippet.missingFields.warning": "Snippet missing title or body", + "listeners.dashboard.snippetListener.addSnippet.exists.warning": "Snippet with the same title already exists", + "listeners.dashboard.snippetListener.updateSnippet.noSnippets.warning": "No snippets to update", + + "listeners.general.gitListener.push.error": "Failed to push submodules.", + + "listeners.panel.dataListener.aiSuggestTaxonomy.noEditor.error": "No active editor", + "listeners.panel.dataListener.aiSuggestTaxonomy.noData.error": "No article data", + "listeners.panel.dataListener.getDataFileEntries.noDataFiles.error": "Couldn't find data file entries", + + + "listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "No active editor", + "listeners.panel.taxonomyListener.aiSuggestTaxonomy.noData.error": "No article data", + + "services.modeSwitch.switchMode.quickPick.placeholder": "Select the mode you want to use", + "services.modeSwitch.switchMode.quickPick.title": "{0}: Mode selection", + "services.modeSwitch.setText.mode": "Mode: {0}", + + "services.pagesParser.parsePages.statusBar.text": "Processing...", + "services.pagesParser.parsePages.file.error": "File error: {0}", + + "services.sponsorAi.getTitles.warning": "The AI title generation took too long. Please try again later.", + "services.sponsorAi.getDescription.warning": "The AI description generation took too long. Please try again later.", + "services.sponsorAi.getTaxonomySuggestions.warning": "The AI taxonomy generation took too long. Please try again later.", + + "services.terminal.openLocalServerTerminal.terminalOption.message": "Starting local server" } \ No newline at end of file diff --git a/scripts/generate-localization-enum.js b/scripts/generate-localization-enum.js index d15f5735..126dde0a 100644 --- a/scripts/generate-localization-enum.js +++ b/scripts/generate-localization-enum.js @@ -1,5 +1,6 @@ const fs = require('fs'); const path = require('path'); +const jsoncParser = require('jsonc-parser'); const camlCase = (str) => { const words = str.split('.'); @@ -15,12 +16,14 @@ const camlCase = (str) => { const enFile = fs.readFileSync(path.join(__dirname, '../l10n/bundle.l10n.json'), 'utf8'); // Parse the EN file - const en = JSON.parse(enFile); + const en = jsoncParser.parse(enFile); const keys = Object.keys(en); // Create an enum file - const enumFile = fs.createWriteStream(path.join(__dirname, '../src/localization/localization.enum.ts')); + const enumFile = fs.createWriteStream( + path.join(__dirname, '../src/localization/localization.enum.ts') + ); // Write the enum file header enumFile.write(`export enum LocalizationKey {\n`); @@ -38,4 +41,4 @@ const camlCase = (str) => { // Close the enum file enumFile.close(); -})(); \ No newline at end of file +})(); diff --git a/src/commands/Article.ts b/src/commands/Article.ts index 44f8d004..f6e36e05 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -29,6 +29,8 @@ import { NavigationType } from '../dashboardWebView/models'; import { processKnownPlaceholders } from '../helpers/PlaceholderHelper'; import { Position } from 'vscode'; import { SNIPPET } from '../constants/Snippet'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Article { /** @@ -80,12 +82,20 @@ export class Article { } if (options.length === 0) { - Notifications.info(`No ${type === TaxonomyType.Tag ? 'tags' : 'categories'} configured.`); + Notifications.info( + l10n.t( + LocalizationKey.commandsArticleNotificationNoTaxonomy, + type === TaxonomyType.Tag ? 'tags' : 'categories' + ) + ); return; } const selectedOptions = await vscode.window.showQuickPick(options, { - placeHolder: `Select your ${type === TaxonomyType.Tag ? 'tags' : 'categories'} to insert`, + placeHolder: l10n.t( + LocalizationKey.commandsArticleQuickPickPlaceholder, + type === TaxonomyType.Tag ? 'tags' : 'categories' + ), canPickMany: true, ignoreFocusOut: true }); @@ -117,7 +127,7 @@ export class Article { ArticleHelper.update(editor, article); } catch (e) { Notifications.error( - `Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.` + l10n.t(LocalizationKey.commandsArticleSetDateError, `${CONFIG_KEY}${SETTING_DATE_FORMAT}`) ); } } @@ -180,7 +190,7 @@ export class Article { return cloneArticle; } catch (e: unknown) { Notifications.error( - `Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.` + l10n.t(LocalizationKey.commandsArticleSetDateError, `${CONFIG_KEY}${SETTING_DATE_FORMAT}`) ); } } @@ -292,7 +302,12 @@ export class Article { overwrite: false }); } catch (e: unknown) { - Notifications.error(`Failed to rename file: ${(e as Error).message || e}`); + Notifications.error( + l10n.t( + LocalizationKey.commandsArticleUpdateSlugError, + ((e as Error).message || e) as string + ) + ); } } } diff --git a/src/commands/Cache.ts b/src/commands/Cache.ts index aafaa7f2..65eb5306 100644 --- a/src/commands/Cache.ts +++ b/src/commands/Cache.ts @@ -1,6 +1,8 @@ import { commands } from 'vscode'; import { COMMAND_NAME, ExtensionState } from '../constants'; import { Extension, Logger, Notifications } from '../helpers'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Cache { public static async registerCommands() { @@ -28,9 +30,9 @@ export class Cache { await ext.setState(ExtensionState.Settings.Extends, undefined, 'workspace', true); if (showNotification) { - Notifications.info('Cache cleared'); + Notifications.info(l10n.t(LocalizationKey.commandsCacheCleared)); } else { - Logger.info('Cache cleared'); + Logger.info(l10n.t(LocalizationKey.commandsCacheCleared)); } } } diff --git a/src/commands/Chatbot.ts b/src/commands/Chatbot.ts index c598d10b..856e3424 100644 --- a/src/commands/Chatbot.ts +++ b/src/commands/Chatbot.ts @@ -2,9 +2,11 @@ import { Telemetry } from './../helpers/Telemetry'; import { TelemetryEvent, PreviewCommands, GeneralCommands } from './../constants'; import { join } from 'path'; import { commands, Uri, ViewColumn, window } from 'vscode'; -import { Extension, Settings } from '../helpers'; +import { Extension } from '../helpers'; import { WebviewHelper } from '@estruyf/vscode'; import { getLocalizationFile } from '../utils/getLocalizationFile'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Chatbot { /** @@ -14,7 +16,7 @@ export class Chatbot { // Create the preview webview const webView = window.createWebviewPanel( 'frontMatterChatbot', - 'Front Matter AI - Ask me anything', + `Front Matter AI - ${l10n.t(LocalizationKey.commandsChatbotTitle)}`, { viewColumn: ViewColumn.Beside, preserveFocus: true diff --git a/src/commands/Content.ts b/src/commands/Content.ts index 6c4ef060..b5201a24 100644 --- a/src/commands/Content.ts +++ b/src/commands/Content.ts @@ -1,6 +1,8 @@ import { commands, QuickPickItem, window } from 'vscode'; import { COMMAND_NAME, SETTING_TEMPLATES_ENABLED } from '../constants'; import { Settings } from '../helpers'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Content { public static async create() { @@ -12,18 +14,18 @@ export class Content { const options: QuickPickItem[] = [ { - label: 'Create content by content type', - description: 'Select if you want to create new content by the available content type(s)' + label: l10n.t(LocalizationKey.commandsContentOptionContentTypeLabel), + description: l10n.t(LocalizationKey.commandsContentOptionContentTypeDescription) }, { - label: 'Create content by template', - description: 'Select if you want to create new content by the available template(s)' + label: l10n.t(LocalizationKey.commandsContentOptionTemplateLabel), + description: l10n.t(LocalizationKey.commandsContentOptionTemplateDescription) } as QuickPickItem ]; const selectedOption = await window.showQuickPick(options, { - title: 'Create content', - placeHolder: `Select how you want to create your new content`, + title: l10n.t(LocalizationKey.commandsContentQuickPickTitle), + placeHolder: l10n.t(LocalizationKey.commandsContentQuickPickPlaceholder), canPickMany: false, ignoreFocusOut: true }); diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index 342b747f..a1286500 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -29,6 +29,8 @@ import { import { MediaListener as PanelMediaListener } from '../listeners/panel'; import { GitListener, ModeListener } from '../listeners/general'; import { Folders } from './Folders'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Dashboard { private static webview: WebviewPanel | null = null; @@ -119,7 +121,7 @@ export class Dashboard { // Create the preview webview Dashboard.webview = window.createWebviewPanel( 'frontMatterDashboard', - 'FrontMatter Dashboard', + `Front Matter ${l10n.t(LocalizationKey.commandsDashboardTitle)}`, ViewColumn.One, { enableScripts: true, diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 68000f43..10f8513b 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -25,6 +25,8 @@ import { Telemetry } from '../helpers/Telemetry'; import { glob } from 'glob'; import { mkdirAsync } from '../utils/mkdirAsync'; import { existsAsync } from '../utils'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export const WORKSPACE_PLACEHOLDER = `[[workspace]]`; @@ -57,15 +59,15 @@ export class Folders { } const folderName = await window.showInputBox({ - title: `Add media folder`, - prompt: `Which name would you like to give to your folder (use "/" to create multi-level folders)?`, + title: l10n.t(LocalizationKey.commandsFoldersAddMediaFolderInputBoxTitle), + prompt: l10n.t(LocalizationKey.commandsFoldersAddMediaFolderInputBoxPrompt), value: startPath, ignoreFocusOut: true, placeHolder: `${format(new Date(), `yyyy/MM`)}` }); if (!folderName) { - Notifications.warning(`No folder name was specified.`); + Notifications.warning(l10n.t(LocalizationKey.commandsFoldersAddMediaFolderNoFolderWarning)); return; } @@ -126,15 +128,15 @@ export class Folders { ); if (exists) { - Notifications.warning(`Folder is already registered`); + Notifications.warning(l10n.t(LocalizationKey.commandsFoldersCreateFolderExistsWarning)); return; } if (!folderName) { folderName = await window.showInputBox({ - title: `Register folder`, - prompt: `Which name would you like to specify for this folder?`, - placeHolder: `Folder name`, + title: l10n.t(LocalizationKey.commandsFoldersCreateInputTitle), + prompt: l10n.t(LocalizationKey.commandsFoldersCreateInputPrompt), + placeHolder: l10n.t(LocalizationKey.commandsFoldersCreateInputPlaceholder), value: basename(folder.fsPath), ignoreFocusOut: true }); @@ -154,7 +156,7 @@ export class Folders { folders = uniqBy(folders, (f) => f.path); await Folders.update(folders); - Notifications.info(`Folder registered`); + Notifications.info(l10n.t(LocalizationKey.commandsFoldersCreateSuccess)); Telemetry.send(TelemetryEvent.registerFolder); @@ -245,7 +247,9 @@ export class Folders { if (!projectFolder) { window .showWorkspaceFolderPick({ - placeHolder: `Please select the main workspace folder for Front Matter to use.` + placeHolder: l10n.t( + LocalizationKey.commandsFoldersGetWorkspaceFolderWorkspaceFolderPickPlaceholder + ) }) .then(async (selectedFolder) => { if (selectedFolder) { @@ -378,14 +382,21 @@ export class Folders { } else { if (folderPath && !existsSync(folderPath)) { Notifications.errorShowOnce( - `Folder "${folder.title} (${folder.path})" does not exist. Please remove it from the settings.`, - 'Remove folder', - 'Create folder' + l10n.t( + LocalizationKey.commandsFoldersGetNotificationErrorTitle, + `${folder.title} (${folder.path})` + ), + l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorRemoveAction), + l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorCreateAction) ).then((answer) => { - if (answer === 'Remove folder') { + if ( + answer === l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorRemoveAction) + ) { const folders = Folders.get(); Folders.update(folders.filter((f) => f.path !== folder.path)); - } else if (answer === 'Create folder') { + } else if ( + answer === l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorCreateAction) + ) { mkdirAsync(folderPath as string, { recursive: true }); } }); diff --git a/src/commands/Preview.ts b/src/commands/Preview.ts index 5b0c3834..0f28e902 100644 --- a/src/commands/Preview.ts +++ b/src/commands/Preview.ts @@ -24,6 +24,8 @@ import { WebviewHelper } from '@estruyf/vscode'; import { Folders } from './Folders'; import { ParsedFrontMatter } from '../parsers'; import { getLocalizationFile } from '../utils/getLocalizationFile'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Preview { public static filePath: string | undefined = undefined; @@ -71,7 +73,9 @@ export class Preview { // Create the preview webview const webView = window.createWebviewPanel( 'frontMatterPreview', - article?.data?.title ? `Preview: ${article?.data?.title}` : 'FrontMatter Preview', + article?.data?.title + ? l10n.t(LocalizationKey.commandsPreviewPanelTitle, article?.data.title) + : 'Front Matter Preview', { viewColumn: ViewColumn.Beside, preserveFocus: true @@ -393,7 +397,7 @@ export class Preview { const folderNames = crntFolders.map((folder) => folder.title); const selectedFolderName = await window.showQuickPick(folderNames, { canPickMany: false, - title: 'Select the folder of the article to preview' + title: l10n.t(LocalizationKey.commandsPreviewAskUserToPickFolderTitle) }); if (selectedFolderName) { diff --git a/src/commands/Project.ts b/src/commands/Project.ts index 2281ae63..0521a3f6 100644 --- a/src/commands/Project.ts +++ b/src/commands/Project.ts @@ -21,6 +21,8 @@ import { } from '../constants'; import { SettingsListener } from '../listeners/dashboard'; import { existsAsync, writeFileAsync } from '../utils'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Project { private static content = `--- @@ -66,7 +68,7 @@ categories: [] if (sampleTemplate !== undefined) { await Project.createSampleTemplate(); } else { - Notifications.info('Project initialized successfully.'); + Notifications.info(l10n.t(LocalizationKey.commandsProjectInitializeSuccess)); } // Initialize the media library @@ -89,10 +91,14 @@ categories: [] } catch (error: unknown) { const err = error as Error; Logger.error(`Project::init: ${err?.message || err}`); - Notifications.error(`Sorry, something went wrong - ${err?.message || err}`); + Notifications.errorWithOutput(l10n.t(LocalizationKey.commonError)); } } + /** + * Project switcher + * @returns + */ public static async switchProject() { const projects = Settings.getProjects(); const project = await window.showQuickPick( @@ -100,7 +106,7 @@ categories: [] { canPickMany: false, ignoreFocusOut: true, - title: 'Select a project to switch to' + title: l10n.t(LocalizationKey.commandsProjectSwitchProjectTitle) } ); @@ -136,7 +142,7 @@ categories: [] await writeFileAsync(article.fsPath, Project.content, { encoding: 'utf-8' }); - Notifications.info('Sample template created.'); + Notifications.info(l10n.t(LocalizationKey.commandsProjectCreateSampleTemplateInfo)); } } diff --git a/src/commands/Settings.ts b/src/commands/Settings.ts index 5190bd5c..f87f7460 100644 --- a/src/commands/Settings.ts +++ b/src/commands/Settings.ts @@ -5,6 +5,8 @@ import { EXTENSION_NAME } from '../constants'; import { ArticleHelper, FilesHelper } from '../helpers'; import { FrontMatterParser } from '../parsers'; import { Notifications } from '../helpers/Notifications'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Settings { /** @@ -13,11 +15,11 @@ export class Settings { * @param type */ public static async create(type: TaxonomyType) { + const taxonomy = type === TaxonomyType.Tag ? 'tag' : 'category'; + const newOption = await vscode.window.showInputBox({ - prompt: `Insert the value of the ${ - type === TaxonomyType.Tag ? 'tag' : 'category' - } that you want to add to your configuration.`, - placeHolder: `Name of the ${type === TaxonomyType.Tag ? 'tag' : 'category'}`, + prompt: l10n.t(LocalizationKey.commandsFoldersCreateInputPrompt, taxonomy), + placeHolder: l10n.t(LocalizationKey.commandsFoldersCreateInputPlaceholder, taxonomy), ignoreFocusOut: true }); @@ -25,9 +27,7 @@ export class Settings { let options = (await TaxonomyHelper.get(type)) || []; if (options.find((o) => o === newOption)) { - Notifications.info( - `The provided ${type === TaxonomyType.Tag ? 'tag' : 'category'} already exists.` - ); + Notifications.warning(l10n.t(LocalizationKey.commandsSettingsCreateWarning, taxonomy)); return; } @@ -35,15 +35,16 @@ export class Settings { TaxonomyHelper.update(type, options); // Ask if the new term needs to be added to the page - const addToPage = await vscode.window.showQuickPick(['yes', 'no'], { - canPickMany: false, - placeHolder: `Do you want to add the new ${ - type === TaxonomyType.Tag ? 'tag' : 'category' - } to the page?`, - ignoreFocusOut: true - }); + const addToPage = await vscode.window.showQuickPick( + [l10n.t(LocalizationKey.commonYes), l10n.t(LocalizationKey.commonNo)], + { + canPickMany: false, + placeHolder: l10n.t(LocalizationKey.commandsSettingsCreateQuickPickPlaceholder, taxonomy), + ignoreFocusOut: true + } + ); - if (addToPage && addToPage === 'yes') { + if (addToPage && addToPage === l10n.t(LocalizationKey.commonYes)) { const editor = vscode.window.activeTextEditor; if (!editor) { return; @@ -54,7 +55,7 @@ export class Settings { return; } - const matterProp: string = type === TaxonomyType.Tag ? 'tags' : 'categories'; + const matterProp: string = taxonomy; // Add the selected options to the options array if (article.data[matterProp]) { const propData: string[] = article.data[matterProp]; @@ -83,7 +84,7 @@ export class Settings { vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, - title: `${EXTENSION_NAME}: exporting tags and categories`, + title: l10n.t(LocalizationKey.commandsSettingsExportProgressTitle, EXTENSION_NAME), cancellable: false }, async (progress) => { @@ -146,7 +147,11 @@ export class Settings { // Done Notifications.info( - `Export completed. Tags: ${crntTags.length} - Categories: ${crntCategories.length}.` + l10n.t( + LocalizationKey.commandsSettingsExportProgressSuccess, + crntTags.length, + crntCategories.length + ) ); } ); @@ -157,8 +162,8 @@ export class Settings { */ public static async remap() { const taxType = await vscode.window.showQuickPick(['Tag', 'Category'], { - title: `Remap`, - placeHolder: `What do you want to remap?`, + title: l10n.t(LocalizationKey.commandsSettingsRemapQuickpickTitle), + placeHolder: l10n.t(LocalizationKey.commandsSettingsRemapQuickpickPlaceholder), canPickMany: false, ignoreFocusOut: true }); @@ -168,15 +173,18 @@ export class Settings { } const type = taxType === 'Tag' ? TaxonomyType.Tag : TaxonomyType.Category; + const taxonomy = type === TaxonomyType.Tag ? 'tags' : 'categories'; const options = (await TaxonomyHelper.get(type)) || []; if (!options || options.length === 0) { - Notifications.info(`No ${type === TaxonomyType.Tag ? 'tags' : 'categories'} configured.`); + Notifications.warning( + l10n.t(LocalizationKey.commandsSettingsRemapNoTaxonomyWarning, taxonomy) + ); return; } const selectedOption = await vscode.window.showQuickPick(options, { - placeHolder: `Select your ${type === TaxonomyType.Tag ? 'tags' : 'categories'} to insert`, + placeHolder: l10n.t(LocalizationKey.commandsSettingsRemapSelectTaxonomyPlaceholder, taxonomy), canPickMany: false, ignoreFocusOut: true }); @@ -186,19 +194,23 @@ export class Settings { } const newOptionValue = await vscode.window.showInputBox({ - prompt: `Specify the value of the ${ - type === TaxonomyType.Tag ? 'tag' : 'category' - } with which you want to remap "${selectedOption}". Leave the input if you want to remove the ${ - type === TaxonomyType.Tag ? 'tag' : 'category' - } from all articles.`, - placeHolder: `Name of the ${type === TaxonomyType.Tag ? 'tag' : 'category'}`, + prompt: l10n.t( + LocalizationKey.commandsSettingsRemapNewOptionInputPrompt, + taxonomy, + selectedOption + ), + placeHolder: l10n.t(LocalizationKey.commandsSettingsRemapNewOptionInputPlaceholder, taxonomy), ignoreFocusOut: true }); if (!newOptionValue) { const deleteAnswer = await vscode.window.showQuickPick(['yes', 'no'], { canPickMany: false, - placeHolder: `Delete ${selectedOption} ${type === TaxonomyType.Tag ? 'tag' : 'category'}?`, + placeHolder: l10n.t( + LocalizationKey.commandsSettingsRemapDeletePlaceholder, + selectedOption, + taxonomy + ), ignoreFocusOut: true }); if (deleteAnswer === 'no') { diff --git a/src/commands/StatusListener.ts b/src/commands/StatusListener.ts index b2585957..c665eac6 100644 --- a/src/commands/StatusListener.ts +++ b/src/commands/StatusListener.ts @@ -1,6 +1,7 @@ import { ParsedFrontMatter } from './../parsers/FrontMatterParser'; import { CONTEXT, + EXTENSION_NAME, NOTIFICATION_TYPE, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, @@ -16,6 +17,8 @@ import { DataListener } from '../listeners/panel'; import { commands } from 'vscode'; import { Field } from '../models'; import { Preview } from './Preview'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class StatusListener { /** @@ -135,12 +138,13 @@ export class StatusListener { const diagnostic: vscode.Diagnostic = { code: '', - message: `This ${fields - .map((f) => f.name) - .join('/')} field is required to contain a value.`, + message: l10n.t( + LocalizationKey.commandsStatusListenerVerifyRequiredFieldsDiagnosticEmptyField, + fields.map((f) => f.name).join('/') + ), range: new vscode.Range(posStart, posEnd), severity: vscode.DiagnosticSeverity.Error, - source: 'Front Matter' + source: EXTENSION_NAME }; requiredDiagnostics.push(diagnostic); @@ -158,7 +162,10 @@ export class StatusListener { Notifications.showIfNotDisabled( NOTIFICATION_TYPE.requiredFieldValidation, 'ERROR_ONCE', - `The following fields are required to contain a value: ${fieldsToReport.join(', ')}` + l10n.t( + LocalizationKey.commandsStatusListenerVerifyRequiredFieldsNotificationError, + fieldsToReport.join(', ') + ) ); } } diff --git a/src/commands/Template.ts b/src/commands/Template.ts index a39e56c7..3ee00cdc 100644 --- a/src/commands/Template.ts +++ b/src/commands/Template.ts @@ -16,6 +16,8 @@ import { PagesListener } from '../listeners/dashboard'; import { extname } from 'path'; import { Telemetry } from '../helpers/Telemetry'; import { writeFileAsync, copyFileAsync } from '../utils'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Template { /** @@ -31,27 +33,30 @@ export class Template { const clonedArticle = Object.assign({}, article); const titleValue = await vscode.window.showInputBox({ - title: `Template title`, - prompt: `What name would you like to give your template?`, - placeHolder: `article`, + title: l10n.t(LocalizationKey.commandsTemplateGenerateInputTitle), + prompt: l10n.t(LocalizationKey.commandsTemplateGenerateInputPrompt), + placeHolder: l10n.t(LocalizationKey.commandsTemplateGenerateInputPlaceholder), ignoreFocusOut: true }); if (!titleValue) { - Notifications.warning(`You did not specify a template title.`); + Notifications.warning(l10n.t(LocalizationKey.commandsTemplateGenerateNoTitleWarning)); return; } - const keepContents = await vscode.window.showQuickPick(['yes', 'no'], { - title: `Keep contents`, - canPickMany: false, - placeHolder: `Do you want to keep the contents for the template?`, - ignoreFocusOut: true - }); + const keepContents = await vscode.window.showQuickPick( + [l10n.t(LocalizationKey.commonYes), l10n.t(LocalizationKey.commonNo)], + { + title: l10n.t(LocalizationKey.commandsTemplateGenerateKeepContentsTitle), + placeHolder: l10n.t(LocalizationKey.commandsTemplateGenerateKeepContentsPlaceholder), + canPickMany: false, + ignoreFocusOut: true + } + ); if (!keepContents) { Notifications.warning( - `You did not pick any of the options for keeping the template its content.` + l10n.t(LocalizationKey.commandsTemplateGenerateKeepContentsNoOptionWarning) ); return; } @@ -60,14 +65,16 @@ export class Template { const templatePath = Project.templatePath(); if (templatePath) { const fileContents = ArticleHelper.stringifyFrontMatter( - keepContents === 'no' ? '' : clonedArticle.content, + keepContents === l10n.t(LocalizationKey.commonNo) ? '' : clonedArticle.content, clonedArticle.data ); const templateFile = path.join(templatePath.fsPath, `${titleValue}.${fileType}`); await writeFileAsync(templateFile, fileContents, { encoding: 'utf-8' }); - Notifications.info(`Template created and is now available in your ${folder} folder.`); + Notifications.info( + l10n.t(LocalizationKey.commandsTemplateGenerateKeepContentsSuccess, folder) + ); } } } @@ -79,7 +86,7 @@ export class Template { const folder = Settings.get(SETTING_TEMPLATES_FOLDER); if (!folder) { - Notifications.warning(`No templates found.`); + Notifications.warning(l10n.t(LocalizationKey.commandsTemplateGetTemplatesWarning)); return; } @@ -96,26 +103,28 @@ export class Template { const contentTypes = ContentType.getAll(); if (!folderPath) { - Notifications.warning(`Incorrect project folder path retrieved.`); + Notifications.warning(l10n.t(LocalizationKey.commandsTemplateCreateFolderPathWarning)); return; } const templates = await Template.getTemplates(); if (!templates || templates.length === 0) { - Notifications.warning(`No templates found.`); + Notifications.warning(l10n.t(LocalizationKey.commandsTemplateCreateNoTemplatesWarning)); return; } const selectedTemplate = await vscode.window.showQuickPick( templates.map((t) => path.basename(t.fsPath)), { - title: `Select a template`, - placeHolder: `Select the content template to use`, + title: l10n.t(LocalizationKey.commandsTemplateCreateSelectTemplateTitle), + placeHolder: l10n.t(LocalizationKey.commandsTemplateCreateSelectTemplatePlaceholder), ignoreFocusOut: true } ); if (!selectedTemplate) { - Notifications.warning(`No template selected.`); + Notifications.warning( + l10n.t(LocalizationKey.commandsTemplateCreateSelectTemplateNoTemplateWarning) + ); return; } @@ -127,7 +136,9 @@ export class Template { // Start the template read const template = templates.find((t) => t.fsPath.endsWith(selectedTemplate)); if (!template) { - Notifications.warning(`Content template could not be found.`); + Notifications.warning( + l10n.t(LocalizationKey.commandsTemplateCreateSelectTemplateNotFoundWarning) + ); return; } @@ -154,7 +165,7 @@ export class Template { // Update the properties inside the template let frontMatter = await ArticleHelper.getFrontMatterByPath(newFilePath); if (!frontMatter) { - Notifications.warning(`Something failed when retrieving the newly created file.`); + Notifications.warning(l10n.t(LocalizationKey.commonError)); return; } @@ -185,7 +196,7 @@ export class Template { vscode.window.showTextDocument(txtDoc); } - Notifications.info(`Your new content has been created.`); + Notifications.info(l10n.t(LocalizationKey.commandsTemplateCreateSuccess)); Telemetry.send(TelemetryEvent.createContentFromTemplate); diff --git a/src/commands/Wysiwyg.ts b/src/commands/Wysiwyg.ts index 1af18107..2c2fb72a 100644 --- a/src/commands/Wysiwyg.ts +++ b/src/commands/Wysiwyg.ts @@ -1,6 +1,8 @@ import { commands, window, Selection, QuickPickItem, TextEditor } from 'vscode'; import { COMMAND_NAME, CONTEXT, SETTING_CONTENT_WYSIWYG } from '../constants'; import { Settings } from '../helpers'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; enum MarkupType { bold = 1, @@ -81,45 +83,45 @@ export class Wysiwyg { commands.registerCommand(COMMAND_NAME.options, async () => { const qpItems: QuickPickItem[] = [ { - label: '$(list-unordered) Unordered list', - detail: 'Add an unordered list', + label: `$(list-unordered) ${LocalizationKey.commandsWysiwygCommandUnorderedListLabel}`, + detail: LocalizationKey.commandsWysiwygCommandUnorderedListDetail, alwaysShow: true }, { - label: '$(list-ordered) Ordered list', - detail: 'Add an ordered list', + label: `$(list-ordered) ${LocalizationKey.commandsWysiwygCommandOrderedListLabel}`, + detail: LocalizationKey.commandsWysiwygCommandOrderedListDetail, alwaysShow: true }, { - label: '$(tasklist) Task list', - detail: 'Add a task list', + label: `$(tasklist) ${LocalizationKey.commandsWysiwygCommandTaskListLabel}`, + detail: LocalizationKey.commandsWysiwygCommandTaskListDetail, alwaysShow: true }, { - label: '$(code) Code', - detail: 'Add inline code snippet', + label: `$(code) ${LocalizationKey.commandsWysiwygCommandCodeLabel}`, + detail: LocalizationKey.commandsWysiwygCommandCodeDetail, alwaysShow: true }, { - label: '$(symbol-namespace) Code block', - detail: 'Add a code block', + label: `$(symbol-namespace) ${LocalizationKey.commandsWysiwygCommandCodeblockLabel}`, + detail: LocalizationKey.commandsWysiwygCommandCodeblockDetail, alwaysShow: true }, { - label: '$(quote) Blockquote', - detail: 'Add a blockquote', + label: `$(quote) ${LocalizationKey.commandsWysiwygCommandBlockquoteLabel}`, + detail: LocalizationKey.commandsWysiwygCommandBlockquoteDetail, alwaysShow: true }, { - label: '$(symbol-text) Strikethrough', - detail: 'Add a strikethrough', + label: `$(symbol-text) ${LocalizationKey.commandsWysiwygCommandStrikethroughLabel}`, + detail: LocalizationKey.commandsWysiwygCommandStrikethroughDetail, alwaysShow: true } ]; const option = await window.showQuickPick([...qpItems], { - title: 'WYSIWYG Options', - placeHolder: 'Which type of markup would you like to insert?', + title: l10n.t(LocalizationKey.commandsWysiwygQuickPickTitle), + placeHolder: l10n.t(LocalizationKey.commandsWysiwygQuickPickPlaceholder), canPickMany: false, ignoreFocusOut: true }); @@ -209,17 +211,17 @@ export class Wysiwyg { const linkText = hasTextSelection ? editor.document.getText(selection) : ''; const link = await window.showInputBox({ - title: 'WYSIWYG Hyperlink', - placeHolder: 'Enter the URL', - prompt: 'Enter the URL', + title: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputTitle), + placeHolder: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputPrompt), + prompt: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputPrompt), value: linkText, ignoreFocusOut: true }); const text = await window.showInputBox({ - title: 'WYSIWYG Text', - prompt: 'Enter the text for the hyperlink', - placeHolder: 'Enter the text for the hyperlink', + title: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkTextInputTitle), + prompt: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkTextInputPrompt), + placeHolder: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkTextInputPrompt), value: linkText, ignoreFocusOut: true }); @@ -278,9 +280,9 @@ export class Wysiwyg { const headingLvl = await window.showQuickPick( ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4', 'Heading 5', 'Heading 6'], { - title: 'Heading Level', + title: l10n.t(LocalizationKey.commandsWysiwygInsertTextHeadingInputTitle), + placeHolder: l10n.t(LocalizationKey.commandsWysiwygInsertTextHeadingInputPlaceholder), canPickMany: false, - placeHolder: 'Which heading level do you want to insert?', ignoreFocusOut: true } ); diff --git a/src/constants/ContentType.ts b/src/constants/ContentType.ts index 258feb09..c8880ecd 100644 --- a/src/constants/ContentType.ts +++ b/src/constants/ContentType.ts @@ -3,7 +3,7 @@ import { ContentType } from './../models/PanelSettings'; export const DEFAULT_CONTENT_TYPE_NAME = 'default'; export const DEFAULT_CONTENT_TYPE: ContentType = { - name: 'default', + name: DEFAULT_CONTENT_TYPE_NAME, pageBundle: false, previewPath: null, fields: [ diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 02a818b8..dbde401b 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -1,4 +1,4 @@ -export const EXTENSION_NAME = 'π–₯π—‹π—ˆπ—‡π— 𝖬𝖺𝗍𝗍𝖾𝗋 𝖒𝖬𝖲'; +export const EXTENSION_NAME = 'Front Matter CMS'; export const CONFIG_KEY = 'frontMatter'; diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index 043e0808..9c8a2f7a 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -4,6 +4,7 @@ export enum DashboardMessage { setPageViewType = 'setPageViewType', getMode = 'getMode', showWarning = 'showWarning', + openConfig = 'openConfig', // Project switching switchProject = 'switchProject', diff --git a/src/dashboardWebView/components/Configuration/Astro/AstroContentTypes.tsx b/src/dashboardWebView/components/Configuration/Astro/AstroContentTypes.tsx index b23339b9..a40c4652 100644 --- a/src/dashboardWebView/components/Configuration/Astro/AstroContentTypes.tsx +++ b/src/dashboardWebView/components/Configuration/Astro/AstroContentTypes.tsx @@ -3,18 +3,20 @@ import * as l10n from '@vscode/l10n'; import { messageHandler } from '@estruyf/vscode/dist/client'; import { DashboardMessage } from '../../../DashboardMessage'; import { AstroCollection } from '../../../../models'; -import { Settings } from '../../../models'; +import { Settings, Status } from '../../../models'; import { SelectItem } from '../../Steps/SelectItem'; import { LocalizationKey } from '../../../../localization'; export interface IAstroContentTypesProps { settings: Settings triggerLoading: (isLoading: boolean) => void; + setStatus: (status: Status) => void; } export const AstroContentTypes: React.FunctionComponent = ({ settings, - triggerLoading + triggerLoading, + setStatus }: React.PropsWithChildren) => { const [collections, setCollections] = React.useState([]); @@ -26,12 +28,26 @@ export const AstroContentTypes: React.FunctionComponent }); }, []); + React.useEffect(() => { + if (collections.length > 0 && settings?.contentTypes?.length > 0) { + // Find created content types from the collections + const astroCollection = collections.find(c => settings.contentTypes.find((ct) => ct.name === c.name)); + console.log(`astroCollection`, astroCollection) + if (astroCollection) { + setStatus(Status.Completed); + } else { + setStatus(Status.Active); + } + } + }, [collections, settings.contentTypes]) + const generateContentType = (collection: AstroCollection) => { triggerLoading(true); messageHandler.request(DashboardMessage.ssgSetAstroContentTypes, { collection }).then((result) => { triggerLoading(false); + setStatus(Status.Completed); }); } diff --git a/src/dashboardWebView/components/SettingsView/SettingsView.tsx b/src/dashboardWebView/components/SettingsView/SettingsView.tsx index fc481240..ee05e6e3 100644 --- a/src/dashboardWebView/components/SettingsView/SettingsView.tsx +++ b/src/dashboardWebView/components/SettingsView/SettingsView.tsx @@ -76,7 +76,8 @@ export const SettingsView: React.FunctionComponent = (_: Rea setLoading(isLoading)} /> + triggerLoading={(isLoading) => setLoading(isLoading)} + setStatus={_ => null} /> ) diff --git a/src/dashboardWebView/components/Steps/SelectItem.tsx b/src/dashboardWebView/components/Steps/SelectItem.tsx index 0adbc032..05ae5d2b 100644 --- a/src/dashboardWebView/components/Steps/SelectItem.tsx +++ b/src/dashboardWebView/components/Steps/SelectItem.tsx @@ -4,7 +4,6 @@ import { CheckCircleIcon as CheckCircleIconSolid, PlusCircleIcon as PlusCircleIc export interface ISelectItemProps { title: string; - icon?: "add" | "select"; buttonTitle: string; isSelected: boolean; disabled?: boolean; @@ -13,7 +12,6 @@ export interface ISelectItemProps { export const SelectItem: React.FunctionComponent = ({ title, - icon = "select", buttonTitle, isSelected, disabled, @@ -30,17 +28,9 @@ export const SelectItem: React.FunctionComponent = ({ disabled={disabled} > {isSelected ? ( - icon === "add" ? ( - - ) : ( - - ) + ) : ( - icon === "add" ? ( - - ) : ( - - ) + )} {title} diff --git a/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx b/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx index 7f4503f2..3ace08a2 100644 --- a/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx +++ b/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx @@ -31,6 +31,7 @@ export const StepsToGetStarted: React.FunctionComponent const [framework, setFramework] = useState(null); const [taxImported, setTaxImported] = useState(false); const [templates, setTemplates] = useState([]); + const [astroCollectionsStatus, setAstroCollectionsStatus] = useState(Status.Optional) const { getColors } = useThemeColors(); const frameworks: Framework[] = FrameworkDetectors.map((detector: any) => detector.framework); @@ -54,6 +55,10 @@ export const StepsToGetStarted: React.FunctionComponent }); } + const showNotification = () => { + Messenger.send(DashboardMessage.openConfig); + }; + const reload = () => { const crntState: any = Messenger.getState() || {}; @@ -175,6 +180,9 @@ export const StepsToGetStarted: React.FunctionComponent )) } + +

+ {l10n.t(LocalizationKey.commonImportant)}: {l10n.t(LocalizationKey.dashboardStepsStepsToGetStartedTemplateWarning)}

), show: (crntTemplates || []).length > 0, @@ -186,10 +194,11 @@ export const StepsToGetStarted: React.FunctionComponent description: ( setLoading(isLoading)} /> + triggerLoading={(isLoading) => setLoading(isLoading)} + setStatus={(status) => setAstroCollectionsStatus(status)} /> ), show: settings.crntFramework === 'astro', - status: Status.Optional + status: astroCollectionsStatus }, { id: `welcome-content-folders`, @@ -255,11 +264,14 @@ export const StepsToGetStarted: React.FunctionComponent : Status.NotStarted, onClick: settings.initialized && settings.contentFolders && settings.contentFolders.length > 0 - ? reload + ? () => { + showNotification(); + reload(); + } : undefined } ] - ), [settings, framework, taxImported, templates]); + ), [settings, framework, taxImported, templates, astroCollectionsStatus]); React.useEffect(() => { if (settings.crntFramework || settings.framework?.name) { diff --git a/src/extension.ts b/src/extension.ts index 8cd35737..f88b54d7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,6 @@ import { GitListener } from './listeners/general/GitListener'; import * as vscode from 'vscode'; -import { COMMAND_NAME, TelemetryEvent } from './constants'; +import { COMMAND_NAME, EXTENSION_NAME, TelemetryEvent } from './constants'; import { MarkdownFoldingProvider } from './providers/MarkdownFoldingProvider'; import { TagType } from './panelWebView/TagType'; import { PanelProvider } from './panelWebView/PanelProvider'; @@ -281,7 +281,7 @@ export async function activate(context: vscode.ExtensionContext) { ); fmStatusBarItem.command = COMMAND_NAME.dashboard; fmStatusBarItem.text = `$(fm-logo)`; - fmStatusBarItem.tooltip = `Front Matter CMS`; + fmStatusBarItem.tooltip = EXTENSION_NAME; fmStatusBarItem.show(); // Register listeners that make sure the status bar updates diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index e4764468..be41208b 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -42,6 +42,8 @@ import { CustomScript } from './CustomScript'; import { Folders } from '../commands/Folders'; import { existsAsync, readFileAsync } from '../utils'; import { mkdirAsync } from '../utils/mkdirAsync'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class ArticleHelper { private static notifiedFiles: string[] = []; @@ -442,7 +444,11 @@ export class ArticleHelper { const newFolder = join(folderPath, sanitizedName); if (await existsAsync(newFolder)) { Notifications.error( - `A page bundle with the name ${sanitizedName} already exists in ${folderPath}` + l10n.t( + LocalizationKey.helpersArticleHelperCreateContentPageBundleError, + sanitizedName, + folderPath + ) ); return; } else { @@ -466,7 +472,9 @@ export class ArticleHelper { await mkdirAsync(folderPath, { recursive: true }); if (await existsAsync(newFilePath)) { - Notifications.warning(`Content with the title already exists. Please specify a new title.`); + Notifications.warning( + l10n.t(LocalizationKey.helpersArticleHelperCreateContentContentExistsWarning) + ); return; } } @@ -597,7 +605,12 @@ export class ArticleHelper { value = value.replace(regex, updatedValue); } } catch (e) { - Notifications.error(`Error while processing the ${placeholder.id} placeholder`); + Notifications.error( + l10n.t( + LocalizationKey.helpersArticleHelperProcessCustomPlaceholdersPlaceholderError, + placeholder.id + ) + ); Logger.error((e as Error).message); value = DefaultFieldValues.faultyCustomPlaceholder; @@ -795,9 +808,10 @@ export class ArticleHelper { Extension.getInstance().diagnosticCollection.set(editor.document.uri, [ { severity: DiagnosticSeverity.Error, - message: `${ - error.name ? `${error.name}: ` : '' - }Error parsing the front matter of ${fileName}`, + message: `${error.name ? `${error.name}: ` : ''}${l10n.t( + LocalizationKey.helpersArticleHelperParseFileDiagnosticError, + fileName + )}`, range: fmRange } ]); diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 7f08999a..dccedf32 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -3,6 +3,7 @@ import { PagesListener } from './../listeners/dashboard'; import { ArticleHelper, CustomScript, Settings } from '.'; import { DefaultFieldValues, + EXTENSION_NAME, FEATURE_FLAG, SETTING_CONTENT_DRAFT_FIELD, SETTING_DATE_FORMAT, @@ -29,6 +30,8 @@ import { processKnownPlaceholders } from './PlaceholderHelper'; import { basename } from 'path'; import { ParsedFrontMatter } from '../parsers'; import { encodeEmoji, existsAsync, writeFileAsync } from '../utils'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class ContentType { /** @@ -173,30 +176,34 @@ export class ContentType { const filePath = editor?.document.uri.fsPath; if (!content || !content.data) { - Notifications.warning(`No front matter data found to generate a content type.`); + Notifications.warning(l10n.t(LocalizationKey.helpersContentTypeGenerateNoFrontMatterError)); return; } - const override = await window.showQuickPick(['Yes', 'No'], { - title: 'Override default content type', - placeHolder: - 'Do you want to overwrite the default content type configuration with the fields used in the current field?', - ignoreFocusOut: true - }); - const overrideBool = override === 'Yes'; + const override = await window.showQuickPick( + [l10n.t(LocalizationKey.commonYes), l10n.t(LocalizationKey.commonNo)], + { + title: l10n.t(LocalizationKey.helpersContentTypeGenerateOverrideQuickPickTitle), + placeHolder: l10n.t(LocalizationKey.helpersContentTypeGenerateOverrideQuickPickPlaceholder), + ignoreFocusOut: true + } + ); + const overrideBool = override === l10n.t(LocalizationKey.commonYes); let contentTypeName: string | undefined = `default`; // Ask for the new content type name if (!overrideBool) { contentTypeName = await window.showInputBox({ - title: 'Generate Content Type', - placeHolder: 'Enter the name of the content type to generate', - prompt: 'Enter the name of the content type to generate', + title: l10n.t(LocalizationKey.helpersContentTypeGenerateContentTypeInputTitle), + placeHolder: l10n.t(LocalizationKey.helpersContentTypeGenerateContentTypeInputPrompt), + prompt: l10n.t(LocalizationKey.helpersContentTypeGenerateContentTypeInputPrompt), ignoreFocusOut: true, validateInput: (value: string) => { if (!value) { - return 'Please enter a name for the content type'; + return l10n.t( + LocalizationKey.helpersContentTypeGenerateContentTypeInputValidationEnterName + ); } const contentTypes = ContentType.getAll(); @@ -204,7 +211,9 @@ export class ContentType { contentTypes && contentTypes.find((ct) => ct.name.toLowerCase() === value.toLowerCase()) ) { - return 'A content type with this name already exists'; + return l10n.t( + LocalizationKey.helpersContentTypeGenerateContentTypeInputValidationNameExists + ); } return null; @@ -212,7 +221,9 @@ export class ContentType { }); if (!contentTypeName) { - Notifications.warning(`You didn't specify a name for the content type.`); + Notifications.warning( + l10n.t(LocalizationKey.helpersContentTypeGenerateNoContentTypeNameWarning) + ); return; } } @@ -221,12 +232,17 @@ export class ContentType { let pageBundle = false; const fileName = filePath ? basename(filePath) : undefined; if (fileName?.startsWith(`index.`)) { - const pageBundleAnswer = await window.showQuickPick(['Yes', 'No'], { - title: 'Use as page bundle', - placeHolder: 'Do you want to use this content type as a page bundle?', - ignoreFocusOut: true - }); - pageBundle = pageBundleAnswer === 'Yes'; + const pageBundleAnswer = await window.showQuickPick( + [l10n.t(LocalizationKey.commonYes), l10n.t(LocalizationKey.commonNo)], + { + title: l10n.t(LocalizationKey.helpersContentTypeGeneratePageBundleQuickPickTitle), + placeHolder: l10n.t( + LocalizationKey.helpersContentTypeGeneratePageBundleQuickPickPlaceHolder + ), + ignoreFocusOut: true + } + ); + pageBundle = pageBundleAnswer === l10n.t(LocalizationKey.commonYes); } const fields = ContentType.generateFields(content.data); @@ -256,11 +272,19 @@ export class ContentType { const configPath = await Settings.projectConfigPath(); const notificationAction = await Notifications.info( - `Content type ${contentTypeName} has been ${overrideBool ? `updated` : `generated`}.`, - configPath && (await existsAsync(configPath)) ? `Open settings` : undefined + overrideBool + ? l10n.t(LocalizationKey.helpersContentTypeGenerateUpdatedSuccess) + : l10n.t(LocalizationKey.helpersContentTypeGenerateGeneratedSuccess), + configPath && (await existsAsync(configPath)) + ? l10n.t(LocalizationKey.commonOpenSettings) + : undefined ); - if (notificationAction === 'Open settings' && configPath && (await existsAsync(configPath))) { + if ( + notificationAction === l10n.t(LocalizationKey.commonOpenSettings) && + configPath && + (await existsAsync(configPath)) + ) { commands.executeCommand('vscode.open', Uri.file(configPath)); } } @@ -278,7 +302,9 @@ export class ContentType { const content = ArticleHelper.getCurrent(); if (!content || !content.data) { - Notifications.warning(`No front matter data found to add missing fields.`); + Notifications.warning( + l10n.t(LocalizationKey.helpersContentTypeAddMissingFieldsNoFrontMatterWarning) + ); return; } @@ -293,11 +319,17 @@ export class ContentType { const configPath = await Settings.projectConfigPath(); const notificationAction = await Notifications.info( - `Content type ${contentType.name} has been updated.`, - configPath && (await existsAsync(configPath)) ? `Open settings` : undefined + l10n.t(LocalizationKey.helpersContentTypeAddMissingFieldsUpdatedSuccess, contentType.name), + configPath && (await existsAsync(configPath)) + ? l10n.t(LocalizationKey.commonOpenSettings) + : undefined ); - if (notificationAction === 'Open settings' && configPath && (await existsAsync(configPath))) { + if ( + notificationAction === l10n.t(LocalizationKey.commonOpenSettings) && + configPath && + (await existsAsync(configPath)) + ) { commands.executeCommand('vscode.open', Uri.file(configPath)); } } @@ -316,16 +348,18 @@ export class ContentType { const contentTypes = ContentType.getAll() || []; if (!content || !content.data) { - Notifications.warning(`No front matter data found to set the content type.`); + Notifications.warning( + l10n.t(LocalizationKey.helpersContentTypeSetContentTypeNoFrontMatterWarning) + ); return; } const ctAnswer = await window.showQuickPick( contentTypes.map((ct) => ct.name), { - title: 'Select the content type', - ignoreFocusOut: true, - placeHolder: 'Which content type would you like to use?' + title: l10n.t(LocalizationKey.helpersContentTypeSetContentTypeQuickPickTitle), + placeHolder: l10n.t(LocalizationKey.helpersContentTypeSetContentTypeQuickPickPlaceholder), + ignoreFocusOut: true } ); @@ -776,7 +810,7 @@ export class ContentType { window.withProgress( { location: ProgressLocation.Notification, - title: 'Front Matter: Creating content...', + title: l10n.t(LocalizationKey.helpersContentTypeCreateProgressTitle, EXTENSION_NAME), cancellable: false }, async () => { @@ -826,7 +860,13 @@ export class ContentType { !!contentType.clearEmpty ); - data = ArticleHelper.updateDates(Object.assign({}, data)); + const article: ParsedFrontMatter = { + content: '', + data: Object.assign({}, data), + path: newFilePath + }; + + data = ArticleHelper.updateDates(article); if (contentType.name !== DEFAULT_CONTENT_TYPE_NAME) { data['type'] = contentType.name; @@ -851,7 +891,7 @@ export class ContentType { await commands.executeCommand('vscode.open', Uri.file(newFilePath)); - Notifications.info(`Your new content has been created.`); + Notifications.info(l10n.t(LocalizationKey.helpersContentTypeCreateSuccess)); Telemetry.send(TelemetryEvent.createContentFromContentType); @@ -992,7 +1032,7 @@ export class ContentType { private static async verify() { const hasFeature = await ModeListener.hasFeature(FEATURE_FLAG.panel.contentType); if (!hasFeature) { - Notifications.warning(`The content type actions are not available in this mode.`); + Notifications.warning(l10n.t(LocalizationKey.helpersContentTypeVerifyWarning)); return false; } diff --git a/src/helpers/CustomScript.ts b/src/helpers/CustomScript.ts index c18566a1..f9f747d4 100644 --- a/src/helpers/CustomScript.ts +++ b/src/helpers/CustomScript.ts @@ -15,6 +15,8 @@ import { ParsedFrontMatter } from '../parsers'; import { TelemetryEvent } from '../constants/TelemetryEvent'; import { SETTING_CUSTOM_SCRIPTS } from '../constants'; import { existsAsync } from '../utils'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class CustomScript { /** @@ -101,7 +103,9 @@ export class CustomScript { } ); } else { - Notifications.warning(`${script.title}: Article couldn't be retrieved.`); + Notifications.warning( + l10n.t(LocalizationKey.helpersCustomScriptSingleRunArticleWarning, script.title) + ); } } @@ -115,7 +119,9 @@ export class CustomScript { const folders = await Folders.getInfo(); if (!folders || folders.length === 0) { - Notifications.warning(`${script.title}: No files found.`); + Notifications.warning( + l10n.t(LocalizationKey.helpersCustomScriptBulkRunNoFilesWarning, script.title) + ); return; } @@ -124,7 +130,7 @@ export class CustomScript { window.withProgress( { location: ProgressLocation.Notification, - title: `Executing: ${script.title}`, + title: l10n.t(LocalizationKey.helpersCustomScriptExecuting, script.title), cancellable: false }, async (progress, token) => { @@ -169,7 +175,9 @@ export class CustomScript { script: ICustomScript ): Promise { if (!path) { - Notifications.error(`${script.title}: There was no folder or media path specified.`); + Notifications.error( + l10n.t(LocalizationKey.helpersCustomScriptRunMediaScriptNoFolderWarning, script.title) + ); return; } @@ -177,7 +185,7 @@ export class CustomScript { window.withProgress( { location: ProgressLocation.Notification, - title: `Executing: ${script.title}`, + title: l10n.t(LocalizationKey.helpersCustomScriptExecuting, script.title), cancellable: false }, async () => { @@ -286,11 +294,15 @@ export class CustomScript { } else if (editor) { await ArticleHelper.update(editor, article); } else { + Logger.error(`Couldn't update article.`); throw new Error(`Couldn't update article.`); } - Notifications.info(`${script.title}: front matter updated.`); + Notifications.info( + l10n.t(LocalizationKey.helpersCustomScriptShowOutputFrontMatterSuccess, script.title) + ); } } else { + Logger.error(`No frontmatter found.`); throw new Error(`No frontmatter found.`); } } catch (error) { @@ -298,16 +310,21 @@ export class CustomScript { ContentProvider.show(output, script.title, script.outputType || 'text'); } else { window - .showInformationMessage(`${script.title}: ${output}`, 'Copy output') + .showInformationMessage( + `${script.title}: ${output}`, + l10n.t(LocalizationKey.helpersCustomScriptShowOutputCopyOutputAction) + ) .then((value) => { - if (value === 'Copy output') { + if (value === l10n.t(LocalizationKey.helpersCustomScriptShowOutputCopyOutputAction)) { vscodeEnv.clipboard.writeText(output); } }); } } } else { - Notifications.info(`${script.title}: Executed your custom script.`); + Notifications.info( + l10n.t(LocalizationKey.helpersCustomScriptShowOutputSuccess, script.title) + ); } } @@ -360,6 +377,7 @@ export class CustomScript { } if (!(await existsAsync(scriptPath))) { + Logger.error(`Script not found: ${scriptPath}`); throw new Error(`Script not found: ${scriptPath}`); } @@ -368,7 +386,7 @@ export class CustomScript { } const fullScript = `${command} "${scriptPath}" ${args}`; - Logger.info(`Executing: ${fullScript}`); + Logger.info(l10n.t(LocalizationKey.helpersCustomScriptExecuting, fullScript)); const output: string = await CustomScript.executeScriptAsync(fullScript, wsPath); @@ -441,7 +459,7 @@ export class CustomScript { return true; } catch (e) { - Logger.error(`Invalid command: ${command}`); + Logger.error(l10n.t(LocalizationKey.helpersCustomScriptValidateCommandError, command)); return false; } } diff --git a/src/helpers/DataFileHelper.ts b/src/helpers/DataFileHelper.ts index d2961222..f23a1bcf 100644 --- a/src/helpers/DataFileHelper.ts +++ b/src/helpers/DataFileHelper.ts @@ -7,6 +7,8 @@ import { commands } from 'vscode'; import { COMMAND_NAME, SETTING_DATA_FILES } from '../constants'; import { Settings } from './SettingsHelper'; import { existsAsync, readFileAsync } from '../utils'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class DataFileHelper { /** @@ -60,15 +62,7 @@ export class DataFileHelper { } } catch (ex) { Logger.error(`DataFileHelper::process: ${(ex as Error).message}`); - const btnClick = await Notifications.error( - `Something went wrong while processing the data file. Check your file and output log for more information.`, - 'Open output' - ); - - if (btnClick && btnClick === 'Open output') { - commands.executeCommand(COMMAND_NAME.showOutputChannel); - } - + Notifications.errorWithOutput(l10n.t(LocalizationKey.helpersDataFileHelperProcessError)); return; } } diff --git a/src/helpers/Extension.ts b/src/helpers/Extension.ts index 8e6ad425..90d5eb72 100644 --- a/src/helpers/Extension.ts +++ b/src/helpers/Extension.ts @@ -33,6 +33,8 @@ import { Notifications } from './Notifications'; import { Settings } from './SettingsHelper'; import { TaxonomyHelper } from './TaxonomyHelper'; import { Cache } from '../commands/Cache'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Extension { private static instance: Extension; @@ -72,8 +74,8 @@ export class Extension { } if (usedVersion !== installedVersion) { - const whatIsNewTitle = `Check the changelog`; - const githubTitle = `Give it a ⭐️`; + const whatIsNewTitle = l10n.t(LocalizationKey.helpersExtensionGetVersionChangelog); + const githubTitle = l10n.t(LocalizationKey.helpersExtensionGetVersionStarIt); const whatIsNew = { title: whatIsNewTitle, @@ -94,7 +96,11 @@ export class Extension { window .showInformationMessage( - `${EXTENSION_NAME} has been updated to v${installedVersion} β€” check out what's new!`, + l10n.t( + LocalizationKey.helpersExtensionGetVersionUpdateNotification, + EXTENSION_NAME, + installedVersion + ), starGitHub, whatIsNew ) @@ -215,11 +221,18 @@ export class Extension { modifiedField?.teamValue ) { Notifications.warning( - `The "${CONFIG_KEY}.${SETTING_DATE_FIELD}" and "${CONFIG_KEY}.${SETTING_MODIFIED_FIELD}" settings have been deprecated. Please use the "isPublishDate" and "isModifiedDate" datetime field properties instead.`, - 'Hide', - 'See migration guide' + l10n.t( + LocalizationKey.helpersExtensionMigrateSettingsDeprecatedWarning, + `${CONFIG_KEY}.${SETTING_DATE_FIELD}`, + `${CONFIG_KEY}.${SETTING_MODIFIED_FIELD}` + ), + l10n.t(LocalizationKey.helpersExtensionMigrateSettingsDeprecatedWarningHide), + l10n.t(LocalizationKey.helpersExtensionMigrateSettingsDeprecatedWarningSeeGuide) ).then(async (value) => { - if (value === 'See migration guide') { + if ( + value === + l10n.t(LocalizationKey.helpersExtensionMigrateSettingsDeprecatedWarningSeeGuide) + ) { const isProd = this.isProductionMode; commands.executeCommand( 'vscode.open', @@ -234,7 +247,9 @@ export class Extension { true, 'workspace' ); - } else if (value === 'Hide') { + } else if ( + value === l10n.t(LocalizationKey.helpersExtensionMigrateSettingsDeprecatedWarningHide) + ) { await Extension.getInstance().setState( ExtensionState.Updates.v7_0_0.dateFields, true, @@ -291,15 +306,23 @@ export class Extension { const templates = await Template.getTemplates(); if (templates && templates.length > 0) { - const answer = await window.showQuickPick(['Yes', 'No'], { - title: 'Front Matter - Templates', - placeHolder: 'Do you want to keep on using the template functionality?', - ignoreFocusOut: true - }); + const answer = await window.showQuickPick( + [l10n.t(LocalizationKey.commonYes), l10n.t(LocalizationKey.commonNo)], + { + title: l10n.t( + LocalizationKey.helpersExtensionMigrateSettingsTemplatesQuickPickTitle, + EXTENSION_NAME + ), + placeHolder: l10n.t( + LocalizationKey.helpersExtensionMigrateSettingsTemplatesQuickPickPlaceholder + ), + ignoreFocusOut: true + } + ); await Settings.update( SETTING_TEMPLATES_ENABLED, - answer?.toLocaleLowerCase() === 'yes', + answer?.toLocaleLowerCase() === l10n.t(LocalizationKey.commonYes), true ); } @@ -410,9 +433,7 @@ export class Extension { const mainVersionInstalled = extensions.getExtension(EXTENSION_ID); if (mainVersionInstalled) { - Notifications.error( - `Front Matter BETA cannot be used while the main version is installed. Please ensure that you have only over version installed.` - ); + Notifications.error(l10n.t(LocalizationKey.helpersExtensionCheckIfExtensionCanRunWarning)); return false; } } diff --git a/src/helpers/MediaHelpers.ts b/src/helpers/MediaHelpers.ts index 744c10b1..af83cfc0 100644 --- a/src/helpers/MediaHelpers.ts +++ b/src/helpers/MediaHelpers.ts @@ -30,6 +30,8 @@ import { MediaListener as DashboardMediaListener } from '../listeners/dashboard' import { ArticleHelper } from './ArticleHelper'; import { lookup } from 'mime-types'; import { existsAsync, readdirAsync, unlinkAsync, writeFileAsync } from '../utils'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class MediaHelpers { private static media: MediaInfo[] = []; @@ -308,7 +310,7 @@ export class MediaHelpers { } if (!(await existsAsync(absFolderPath))) { - Notifications.error(`We couldn't find your selected folder.`); + Notifications.error(l10n.t(LocalizationKey.helpersMediaHelperSaveFileFolderError)); return; } @@ -317,12 +319,22 @@ export class MediaHelpers { if (imgData) { await writeFileAsync(staticPath, imgData.data); - Notifications.info(`File ${fileName} uploaded to: ${folder}`); + Notifications.info( + l10n.t( + LocalizationKey.helpersMediaHelperSaveFileFileUploadedSuccess, + fileName, + folder || '' + ) + ); return true; } else { - Notifications.error(`Something went wrong uploading ${fileName}`); - throw new Error(`Something went wrong uploading ${fileName}`); + Notifications.error( + l10n.t(LocalizationKey.helpersMediaHelperSaveFileFileUploadedFailed, fileName) + ); + throw new Error( + l10n.t(LocalizationKey.helpersMediaHelperSaveFileFileUploadedFailed, fileName) + ); } } @@ -355,8 +367,12 @@ export class MediaHelpers { MediaHelpers.media = []; return true; } catch (err: any) { - Notifications.error(`Something went wrong deleting ${basename(file)}`); - throw new Error(`Something went wrong deleting ${basename(file)}`); + Notifications.error( + l10n.t(LocalizationKey.helpersMediaHelperDeleteFileFileDeletionFailed, basename(file)) + ); + throw new Error( + l10n.t(LocalizationKey.helpersMediaHelperDeleteFileFileDeletionFailed, basename(file)) + ); } } diff --git a/src/helpers/MediaLibrary.ts b/src/helpers/MediaLibrary.ts index 60b70bcf..86cae58f 100644 --- a/src/helpers/MediaLibrary.ts +++ b/src/helpers/MediaLibrary.ts @@ -9,6 +9,8 @@ import { LocalStore } from '../constants'; import { existsAsync, renameAsync } from '../utils'; import { existsSync, mkdirSync, renameSync } from 'fs'; import { lookup } from 'mime-types'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; interface MediaRecord { description: string; @@ -160,14 +162,14 @@ export class MediaLibrary { const newPath = join(dirname(filePath), `${newFileInfo.name}${oldFileInfo.ext}`); if (await existsAsync(newPath)) { - Notifications.warning(`The name "${filename}" already exists at the file location.`); + Notifications.warning(LocalizationKey.helpersMediaLibraryRemoveWarning, filename); } else { await renameAsync(filePath, newPath); await this.rename(filePath, newPath); MediaHelpers.resetMedia(); } } catch (err) { - Notifications.error(`Something went wrong updating "${name}" to "${filename}".`); + Notifications.error(l10n.t(LocalizationKey.helpersMediaLibraryRemoveError, name, filename)); } } } diff --git a/src/helpers/Notifications.ts b/src/helpers/Notifications.ts index 62b07cf0..49b4fd74 100644 --- a/src/helpers/Notifications.ts +++ b/src/helpers/Notifications.ts @@ -1,8 +1,10 @@ import { SETTING_GLOBAL_NOTIFICATIONS_DISABLED } from './../constants/settings'; import { window } from 'vscode'; -import { EXTENSION_NAME, SETTING_GLOBAL_NOTIFICATIONS } from '../constants'; +import { COMMAND_NAME, EXTENSION_NAME, SETTING_GLOBAL_NOTIFICATIONS } from '../constants'; import { Logger } from './Logger'; import { Settings } from './SettingsHelper'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; type NotificationType = 'INFO' | 'WARNING' | 'ERROR' | 'ERROR_ONCE'; @@ -57,6 +59,30 @@ export class Notifications { return Promise.resolve(undefined); } + /** + * Show an error notification to the user with a link to the output channel + * @param message + * @param items + * @returns + */ + public static errorWithOutput(message: string, ...items: any): Thenable { + Logger.info(`${EXTENSION_NAME}: ${message}`, 'ERROR'); + + if (this.shouldShow('ERROR')) { + return window.showErrorMessage( + `${EXTENSION_NAME}: ${message} ${l10n.t( + LocalizationKey.notificationsOutputChannelDescription, + `[${l10n.t(LocalizationKey.notificationsOutputChannelLink)}](command:${ + COMMAND_NAME.showOutputChannel + })` + )}`, + ...items + ); + } + + return Promise.resolve(undefined); + } + /** * Show an error notification to the user only once * @param message diff --git a/src/helpers/Questions.ts b/src/helpers/Questions.ts index 2bd347d6..c72c630e 100644 --- a/src/helpers/Questions.ts +++ b/src/helpers/Questions.ts @@ -6,6 +6,8 @@ import { Notifications } from './Notifications'; import { Settings } from './SettingsHelper'; import { Logger } from './Logger'; import { SponsorAi } from '../services/SponsorAI'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Questions { /** @@ -14,12 +16,15 @@ export class Questions { * @returns */ public static async yesOrNo(placeholder: string) { - const answer = await window.showQuickPick(['yes', 'no'], { - placeHolder: placeholder, - canPickMany: false, - ignoreFocusOut: true - }); - return answer === 'yes'; + const answer = await window.showQuickPick( + [l10n.t(LocalizationKey.commonYes), l10n.t(LocalizationKey.commonNo)], + { + placeHolder: placeholder, + canPickMany: false, + ignoreFocusOut: true + } + ); + return answer === l10n.t(LocalizationKey.commonYes); } /** @@ -36,9 +41,9 @@ export class Questions { if (githubAuth && githubAuth.account.label) { title = await window.showInputBox({ - title: 'Title or description', - prompt: `What would you like to write about?`, - placeHolder: `Content title or description`, + title: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputTitle), + prompt: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputPrompt), + placeHolder: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputPlaceholder), ignoreFocusOut: true }); @@ -49,14 +54,18 @@ export class Questions { if (aiTitles && aiTitles.length > 0) { const options: QuickPickItem[] = [ { - label: `✏️ your title/description`, + label: `✏️ ${l10n.t( + LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickTitleSeparator + )}`, kind: QuickPickItemKind.Separator }, { label: title }, { - label: `πŸ€– AI generated title`, + label: `πŸ€– ${l10n.t( + LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickAiSeparator + )}`, kind: QuickPickItemKind.Separator }, ...aiTitles.map((d: string) => ({ @@ -65,8 +74,10 @@ export class Questions { ]; const selectedTitle = await window.showQuickPick(options, { - title: 'Select a title', - placeHolder: `Select a title for your content`, + title: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputSelectTitle), + placeHolder: l10n.t( + LocalizationKey.helpersQuestionsContentTitleAiInputSelectPlaceholder + ), ignoreFocusOut: true }); @@ -79,13 +90,11 @@ export class Questions { } } catch (e) { Logger.error((e as Error).message); - Notifications.error( - `Failed fetching the AI title. Please try to use your own title or try again later.` - ); + Notifications.error(l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputFailed)); title = undefined; } } else if (!title && showWarning) { - Notifications.warning(`You did not specify a title for your content.`); + Notifications.warning(l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputWarning)); return; } } @@ -93,15 +102,15 @@ export class Questions { if (!title) { title = await window.showInputBox({ - title: 'Title', - prompt: `What would you like to use as a title for the content to create?`, - placeHolder: `Content title`, + title: l10n.t(LocalizationKey.helpersQuestionsContentTitleTitleInputTitle), + prompt: l10n.t(LocalizationKey.helpersQuestionsContentTitleTitleInputPrompt), + placeHolder: l10n.t(LocalizationKey.helpersQuestionsContentTitleTitleInputPlaceholder), ignoreFocusOut: true }); } if (!title && showWarning) { - Notifications.warning(`You did not specify a title for your content.`); + Notifications.warning(l10n.t(LocalizationKey.helpersQuestionsContentTitleTitleInputWarning)); return; } @@ -123,20 +132,26 @@ export class Questions { selectedFolder = await window.showQuickPick( folders.map((f) => f.title), { - title: `Select a folder`, - placeHolder: `Select where you want to create your content`, + title: l10n.t(LocalizationKey.helpersQuestionsSelectContentFolderQuickPickTitle), + placeHolder: l10n.t( + LocalizationKey.helpersQuestionsSelectContentFolderQuickPickPlaceholder + ), ignoreFocusOut: true } ); } else if (folders.length === 1) { selectedFolder = folders[0].title; } else { - Notifications.warning(`No page folders were configures.`); + Notifications.warning( + l10n.t(LocalizationKey.helpersQuestionsSelectContentFolderQuickPickNoFoldersWarning) + ); return; } if (!selectedFolder && showWarning) { - Notifications.warning(`You didn't select a place where you wanted to create your content.`); + Notifications.warning( + l10n.t(LocalizationKey.helpersQuestionsSelectContentFolderQuickPickNoSelectionWarning) + ); return; } @@ -155,7 +170,9 @@ export class Questions { ): Promise { let contentTypes = ContentType.getAll(); if (!contentTypes || contentTypes.length === 0) { - Notifications.warning('No content types found. Please create a content type first.'); + Notifications.warning( + l10n.t(LocalizationKey.helpersQuestionsSelectContentTypeNoContentTypeWarning) + ); return; } @@ -175,14 +192,16 @@ export class Questions { })); const selectedOption = await window.showQuickPick(options, { - title: `Content type`, - placeHolder: `Select the content type to create your new content`, + title: l10n.t(LocalizationKey.helpersQuestionsSelectContentTypeQuickPickTitle), + placeHolder: l10n.t(LocalizationKey.helpersQuestionsSelectContentTypeQuickPickPlaceholder), canPickMany: false, ignoreFocusOut: true }); if (!selectedOption && showWarning) { - Notifications.warning('No content type was selected.'); + Notifications.warning( + l10n.t(LocalizationKey.helpersQuestionsSelectContentTypeNoSelectionWarning) + ); return; } diff --git a/src/helpers/SeoHelper.ts b/src/helpers/SeoHelper.ts index 7cba1795..f19c7bb0 100644 --- a/src/helpers/SeoHelper.ts +++ b/src/helpers/SeoHelper.ts @@ -1,6 +1,9 @@ import * as vscode from 'vscode'; import { ArticleHelper } from '.'; import { ParsedFrontMatter } from '../parsers'; +import { EXTENSION_NAME } from '../constants'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class SeoHelper { public static checkLength( @@ -23,10 +26,15 @@ export class SeoHelper { const diagnostic: vscode.Diagnostic = { code: '', - message: `Article ${fieldName} is longer than ${length} characters (current length: ${value.length}). For SEO reasons, it would be better to make it less than ${length} characters.`, + message: l10n.t( + LocalizationKey.helpersSeoHelperCheckLengthDiagnosticMessage, + fieldName, + length, + value.length + ), range: new vscode.Range(posStart, posEnd), severity: vscode.DiagnosticSeverity.Warning, - source: 'Front Matter' + source: EXTENSION_NAME }; if (collection.has(editor.document.uri)) { diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 04467855..a4bb7c15 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -51,6 +51,8 @@ import { GitListener } from '../listeners/general'; import { DataListener } from '../listeners/panel'; import { MarkdownFoldingProvider } from '../providers/MarkdownFoldingProvider'; import { ModeSwitch } from '../services/ModeSwitch'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class Settings { public static globalFile = 'frontmatter.json'; @@ -181,16 +183,19 @@ export class Settings { if (Settings.hasSettings()) { window .showInformationMessage( - `You have local settings. Would you like to promote them to the global settings ("frontmatter.json")?`, - 'Yes', - 'No' + l10n.t(LocalizationKey.helpersSettingsHelperCheckToPromoteMessage), + l10n.t(LocalizationKey.commonYes), + l10n.t(LocalizationKey.commonNo) ) .then(async (result) => { - if (result === 'Yes') { + if (result === l10n.t(LocalizationKey.commonYes)) { Settings.promote(); } - if (result === 'No' || result === 'Yes') { + if ( + result === l10n.t(LocalizationKey.commonNo) || + result === l10n.t(LocalizationKey.commonYes) + ) { Extension.getInstance().setState(ExtensionState.SettingPromoted, true, 'workspace'); } }); @@ -529,7 +534,7 @@ export class Settings { } } - Notifications.info(`All settings promoted to team level.`); + Notifications.info(l10n.t(LocalizationKey.helpersSettingsHelperPromoteSuccess)); Telemetry.send(TelemetryEvent.promoteSettings); } @@ -663,7 +668,7 @@ export class Settings { await window.withProgress( { location: vscode.ProgressLocation.Notification, - title: `${EXTENSION_NAME}: Reading dynamic config file...` + title: l10n.t(LocalizationKey.helpersSettingsHelperReadConfigProgressTitle) }, async () => { const absFilePath = Folders.getAbsFilePath(dynamicConfigPath); @@ -691,9 +696,7 @@ export class Settings { } } catch (e) { Settings.globalConfig = undefined; - Notifications.error( - `Error reading "frontmatter.json" config file. Check [output window](command:${COMMAND_NAME.showOutputChannel}) for more details.` - ); + Notifications.errorWithOutput(l10n.t(LocalizationKey.helpersSettingsHelperReadConfigError)); Logger.error((e as Error).message); } @@ -1103,7 +1106,7 @@ export class Settings { */ private static async refreshConfig() { await Settings.reloadConfig(); - Notifications.info(`Settings have been refreshed.`); + Notifications.info(l10n.t(LocalizationKey.helpersSettingsHelperRefreshConfigSuccess)); } /** diff --git a/src/helpers/TaxonomyHelper.ts b/src/helpers/TaxonomyHelper.ts index 69c2a918..230584cf 100644 --- a/src/helpers/TaxonomyHelper.ts +++ b/src/helpers/TaxonomyHelper.ts @@ -22,6 +22,8 @@ import { Folders } from '../commands'; import { join } from 'path'; import { SettingsListener as PanelSettingsListener } from '../listeners/panel'; import { SettingsListener as DashboardSettingsListener } from '../listeners/dashboard'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class TaxonomyHelper { private static db: JsonDB; @@ -126,15 +128,15 @@ export class TaxonomyHelper { const { type, value } = data; const answer = await window.showInputBox({ - title: `Rename the "${value}"`, + title: l10n.t(LocalizationKey.helpersTaxonomyHelperRenameInputTitle, value), value, validateInput: (text) => { if (text === value) { - return 'The new value must be different from the old one.'; + return l10n.t(LocalizationKey.helpersTaxonomyHelperRenameValidateEqualValue); } if (!text) { - return 'A new value must be provided.'; + return l10n.t(LocalizationKey.helpersTaxonomyHelperRenameValidateNoValue); } return null; @@ -168,8 +170,8 @@ export class TaxonomyHelper { const answer = await window.showQuickPick( options.filter((o) => o !== value), { - title: `Merge the "${value}" with another ${type} value`, - placeHolder: `Select the ${type} value to merge with`, + title: l10n.t(LocalizationKey.helpersTaxonomyHelperMergeQuickPickTitle, value, type), + placeHolder: l10n.t(LocalizationKey.helpersTaxonomyHelperMergeQuickPickPlaceholder, type), ignoreFocusOut: true } ); @@ -188,13 +190,20 @@ export class TaxonomyHelper { public static async delete(data: { type: string; value: string }) { const { type, value } = data; - const answer = await window.showQuickPick(['Yes', 'No'], { - title: `Delete the "${value}" ${type} value`, - placeHolder: `Are you sure you want to delete the "${value}" ${type} value?`, - ignoreFocusOut: true - }); + const answer = await window.showQuickPick( + [l10n.t(LocalizationKey.commonYes), l10n.t(LocalizationKey.commonNo)], + { + title: l10n.t(LocalizationKey.helpersTaxonomyHelperDeleteQuickPickTitle, value, type), + placeHolder: l10n.t( + LocalizationKey.helpersTaxonomyHelperDeleteQuickPickPlaceholder, + value, + type + ), + ignoreFocusOut: true + } + ); - if (!answer || answer === 'No') { + if (!answer || answer === l10n.t(LocalizationKey.commonNo)) { return; } @@ -221,16 +230,16 @@ export class TaxonomyHelper { const options = await this.getTaxonomyOptions(taxonomyType); const newOption = await window.showInputBox({ - title: `Create a new ${type} value`, - placeHolder: `The value you want to add`, + title: l10n.t(LocalizationKey.helpersTaxonomyHelperCreateNewInputTitle, type), + placeHolder: l10n.t(LocalizationKey.helpersTaxonomyHelperCreateNewInputPlaceholder), ignoreFocusOut: true, validateInput: (text) => { if (!text) { - return 'A value must be provided.'; + return l10n.t(LocalizationKey.helpersTaxonomyHelperCreateNewInputValidateNoValue); } if (options.includes(text)) { - return 'The value already exists.'; + return l10n.t(LocalizationKey.helpersTaxonomyHelperCreateNewInputValidateExists); } return null; @@ -276,11 +285,28 @@ export class TaxonomyHelper { let progressText = ``; if (type === 'edit') { - progressText = `${EXTENSION_NAME}: Renaming "${oldValue}" from ${taxonomyName} to "${newValue}".`; + progressText = l10n.t( + LocalizationKey.helpersTaxonomyHelperProcessEdit, + EXTENSION_NAME, + oldValue, + taxonomyName, + newValue || '' + ); } else if (type === 'merge') { - progressText = `${EXTENSION_NAME}: Merging "${oldValue}" from "${taxonomyName}" to "${newValue}".`; + progressText = l10n.t( + LocalizationKey.helpersTaxonomyHelperProcessMerge, + EXTENSION_NAME, + oldValue, + taxonomyName, + newValue || '' + ); } else if (type === 'delete') { - progressText = `${EXTENSION_NAME}: Deleting "${oldValue}" from "${taxonomyName}".`; + progressText = l10n.t( + LocalizationKey.helpersTaxonomyHelperProcessDelete, + EXTENSION_NAME, + oldValue, + taxonomyName + ); } window.withProgress( @@ -350,11 +376,11 @@ export class TaxonomyHelper { await this.addToSettings(taxonomyType, oldValue, newValue); if (type === 'edit') { - Notifications.info(`Edit completed.`); + Notifications.info(l10n.t(LocalizationKey.helpersTaxonomyHelperProcessEditSuccess)); } else if (type === 'merge') { - Notifications.info(`Merge completed.`); + Notifications.info(l10n.t(LocalizationKey.helpersTaxonomyHelperProcessMergeSuccess)); } else if (type === 'delete') { - Notifications.info(`Deletion completed.`); + Notifications.info(l10n.t(LocalizationKey.helpersTaxonomyHelperProcessDeleteSuccess)); } } ); @@ -375,8 +401,8 @@ export class TaxonomyHelper { options = options.filter((o) => o !== type); const answer = await window.showQuickPick(options, { - title: `Move the "${value}" to another type`, - placeHolder: `Select the type to move to`, + title: l10n.t(LocalizationKey.helpersTaxonomyHelperMoveQuickPickTitle, value), + placeHolder: l10n.t(LocalizationKey.helpersTaxonomyHelperMoveQuickPickPlaceholder), ignoreFocusOut: true }); @@ -390,7 +416,13 @@ export class TaxonomyHelper { window.withProgress( { location: ProgressLocation.Notification, - title: `${EXTENSION_NAME}: Moving "${value}" from ${type} to "${answer}".`, + title: l10n.t( + LocalizationKey.helpersTaxonomyHelperMoveProgressTitle, + EXTENSION_NAME, + value, + type, + answer + ), cancellable: false }, async (progress) => { @@ -465,7 +497,7 @@ export class TaxonomyHelper { await this.process('delete', oldType, value); - Notifications.info(`Move completed.`); + Notifications.info(l10n.t(LocalizationKey.helpersTaxonomyHelperMoveSuccess)); } ); } diff --git a/src/helpers/openFileInEditor.ts b/src/helpers/openFileInEditor.ts index e4b6dc07..03d5c09d 100644 --- a/src/helpers/openFileInEditor.ts +++ b/src/helpers/openFileInEditor.ts @@ -1,6 +1,8 @@ import { Uri, workspace, window } from 'vscode'; import { Logger } from './Logger'; import { Notifications } from './Notifications'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export const openFileInEditor = async (filePath: string) => { if (filePath) { @@ -8,7 +10,7 @@ export const openFileInEditor = async (filePath: string) => { const doc = await workspace.openTextDocument(Uri.file(filePath)); await window.showTextDocument(doc, 1, false); } catch (e) { - Notifications.error(`Couldn't open the file.`); + Notifications.error(l10n.t(LocalizationKey.helpersOpenFileInEditorError)); Logger.error(`${filePath}: ${(e as Error).message}`); } } diff --git a/src/listeners/dashboard/BaseListener.ts b/src/listeners/dashboard/BaseListener.ts index 6e3fd26f..4e9d6682 100644 --- a/src/listeners/dashboard/BaseListener.ts +++ b/src/listeners/dashboard/BaseListener.ts @@ -1,6 +1,5 @@ import { Dashboard } from '../../commands/Dashboard'; import { DashboardCommand } from '../../dashboardWebView/DashboardCommand'; -import { DashboardMessage } from '../../dashboardWebView/DashboardMessage'; import { Logger } from '../../helpers/Logger'; import { PostMessageData } from '../../models'; diff --git a/src/listeners/dashboard/DashboardListener.ts b/src/listeners/dashboard/DashboardListener.ts index 0ba85e54..754bc22f 100644 --- a/src/listeners/dashboard/DashboardListener.ts +++ b/src/listeners/dashboard/DashboardListener.ts @@ -2,10 +2,14 @@ import { Dashboard } from '../../commands/Dashboard'; import { ExtensionState } from '../../constants'; import { DashboardCommand } from '../../dashboardWebView/DashboardCommand'; import { DashboardMessage } from '../../dashboardWebView/DashboardMessage'; -import { Extension, Notifications } from '../../helpers'; +import { Extension, Notifications, Settings } from '../../helpers'; import { PostMessageData } from '../../models'; import { PinnedItems } from '../../services'; import { BaseListener } from './BaseListener'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../../localization'; +import { Uri, commands } from 'vscode'; +import { existsAsync } from '../../utils'; export class DashboardListener extends BaseListener { /** @@ -27,6 +31,9 @@ export class DashboardListener extends BaseListener { case DashboardMessage.setPageViewType: Extension.getInstance().setState(ExtensionState.PagesView, msg.payload, 'workspace'); break; + case DashboardMessage.openConfig: + this.openConfig(); + break; case DashboardMessage.showWarning: Notifications.warning(msg.payload); break; @@ -42,6 +49,21 @@ export class DashboardListener extends BaseListener { } } + private static async openConfig() { + const answer = await Notifications.info( + l10n.t(LocalizationKey.listenersDashboardDashboardListenerOpenConfigNotification), + l10n.t(LocalizationKey.commonOpenSettings) + ); + + if (answer && answer === l10n.t(LocalizationKey.commonOpenSettings)) { + const configPath = await Settings.projectConfigPath(); + + if (configPath && (await existsAsync(configPath))) { + commands.executeCommand('vscode.open', Uri.file(configPath)); + } + } + } + /** * Get the pinned items * @param msg @@ -66,14 +88,22 @@ export class DashboardListener extends BaseListener { const path = payload; if (!path) { - this.sendError(command as any, requestId, 'No path provided.'); + this.sendError( + command as any, + requestId, + l10n.t(LocalizationKey.listenersDashboardDashboardListenerPinItemNoPathError) + ); return; } const allPinned = await PinnedItems.pin(path); if (!allPinned) { - this.sendError(command as any, requestId, 'Could not pin item.'); + this.sendError( + command as any, + requestId, + l10n.t(LocalizationKey.listenersDashboardDashboardListenerPinItemCoundNotPinError) + ); return; } @@ -92,14 +122,22 @@ export class DashboardListener extends BaseListener { const path = payload; if (!path) { - this.sendError(command as any, requestId, 'No path provided.'); + this.sendError( + command as any, + requestId, + l10n.t(LocalizationKey.listenersDashboardDashboardListenerPinItemNoPathError) + ); return; } const updatedPinned = await PinnedItems.remove(path); if (!updatedPinned) { - this.sendError(command as any, requestId, 'Could not unpin item.'); + this.sendError( + command as any, + requestId, + l10n.t(LocalizationKey.listenersDashboardDashboardListenerPinItemCoundNotUnPinError) + ); return; } diff --git a/src/listeners/dashboard/SettingsListener.ts b/src/listeners/dashboard/SettingsListener.ts index ad0a7375..bcbad6bc 100644 --- a/src/listeners/dashboard/SettingsListener.ts +++ b/src/listeners/dashboard/SettingsListener.ts @@ -201,7 +201,9 @@ export class SettingsListener extends BaseListener { await window.withProgress( { location: ProgressLocation.Notification, - title: 'Downloading and initializing the template...', + title: l10n.t( + LocalizationKey.listenersDashboardSettingsListenerTriggerTemplateProgressTitle + ), cancellable: false }, async (progress) => { @@ -209,7 +211,11 @@ export class SettingsListener extends BaseListener { const ghFolder = await download(template.url, wsFolder.fsPath); if (!ghFolder.downloaded) { - Notifications.error('Failed to download the template.'); + Notifications.error( + l10n.t( + LocalizationKey.listenersDashboardSettingsListenerTriggerTemplateDownloadError + ) + ); return; } @@ -225,7 +231,9 @@ export class SettingsListener extends BaseListener { ); } } catch (e) { - Notifications.error(`Failed to initialize the template.`); + Notifications.error( + l10n.t(LocalizationKey.listenersDashboardSettingsListenerTriggerTemplateInitError) + ); } } ); diff --git a/src/listeners/dashboard/SnippetListener.ts b/src/listeners/dashboard/SnippetListener.ts index 5c699a0f..56572285 100644 --- a/src/listeners/dashboard/SnippetListener.ts +++ b/src/listeners/dashboard/SnippetListener.ts @@ -7,6 +7,8 @@ import { Notifications, Settings, Telemetry } from '../../helpers'; import { PostMessageData, Snippets } from '../../models'; import { BaseListener } from './BaseListener'; import { SettingsListener } from './SettingsListener'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../../localization'; export class SnippetListener extends BaseListener { public static process(msg: PostMessageData) { @@ -30,13 +32,17 @@ export class SnippetListener extends BaseListener { const { title, description, body, fields, isMediaSnippet } = data; if (!title || !body) { - Notifications.warning('Snippet missing title or body'); + Notifications.warning( + l10n.t(LocalizationKey.listenersDashboardSnippetListenerAddSnippetMissingFieldsWarning) + ); return; } const snippets = Settings.get(SETTING_CONTENT_SNIPPETS); if (snippets && snippets[title]) { - Notifications.warning('Snippet with the same title already exists'); + Notifications.warning( + l10n.t(LocalizationKey.listenersDashboardSnippetListenerAddSnippetExistsWarning) + ); return; } @@ -63,7 +69,9 @@ export class SnippetListener extends BaseListener { const { snippets } = data; if (!snippets) { - Notifications.warning('No snippets to update'); + Notifications.warning( + l10n.t(LocalizationKey.listenersDashboardSnippetListenerUpdateSnippetNoSnippetsWarning) + ); return; } diff --git a/src/listeners/dashboard/SsgListener.ts b/src/listeners/dashboard/SsgListener.ts index 3460c386..c82292cf 100644 --- a/src/listeners/dashboard/SsgListener.ts +++ b/src/listeners/dashboard/SsgListener.ts @@ -5,10 +5,15 @@ import { BaseListener } from './BaseListener'; import { exec } from 'child_process'; import { Extension, Logger, Settings } from '../../helpers'; import { Folders } from '../../commands'; -import { SETTING_TAXONOMY_CONTENT_TYPES, SsgScripts } from '../../constants'; +import { + DEFAULT_CONTENT_TYPE_NAME, + SETTING_TAXONOMY_CONTENT_TYPES, + SsgScripts +} from '../../constants'; import { SettingsListener } from './SettingsListener'; import { Terminal } from '../../services'; import { existsAsync, readFileAsync } from '../../utils'; +import { join } from 'path'; export class SsgListener extends BaseListener { /** @@ -57,7 +62,16 @@ export class SsgListener extends BaseListener { } } - const contentTypes = Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || []; + // Set the preview image on the first found image of the content type + const images = contentType.fields.filter((f) => f.type === 'image'); + if (images.length > 0) { + images[0].isPreviewImage = true; + } + + let contentTypes = Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || []; + + // Filter out the default content type + contentTypes = contentTypes.filter((ct) => ct.name !== DEFAULT_CONTENT_TYPE_NAME); if (contentTypes.find((ct) => ct.name === collection.name)) { SsgListener.sendRequest(command as any, requestId, {}); @@ -133,7 +147,28 @@ export class SsgListener extends BaseListener { const tempLocation = Uri.joinPath(wsFolder, '/.frontmatter/temp'); const tempScriptPath = Uri.joinPath(tempLocation, SsgScripts.astroContentCollectionScript); await workspace.fs.createDirectory(tempLocation); - workspace.fs.copy(scriptPath, tempScriptPath, { overwrite: true }); + + // Check if the workspace uses pnpm + if (await existsAsync(Uri.joinPath(wsFolder, 'node_modules/.pnpm').fsPath)) { + const vitePackageFiles = await workspace.findFiles( + `**/node_modules/.pnpm/vite@*/node_modules/vite/package.json` + ); + if (vitePackageFiles.length > 0) { + const vitePackageFile = vitePackageFiles[0]; + const vitePackage = JSON.parse(await readFileAsync(vitePackageFile.fsPath, 'utf8')) as { + main: string; + }; + const viteFolder = vitePackageFile.fsPath.replace('/package.json', ''); + const vitePath = join(viteFolder, vitePackage.main).replace(wsFolder.fsPath, '../..'); + + // Update the vite reference, as it is not a direct dependency of the project + let scriptContents = await readFileAsync(scriptPath.fsPath, 'utf8'); + scriptContents = scriptContents.replace(`'vite'`, `'${vitePath}'`); + await workspace.fs.writeFile(tempScriptPath, Buffer.from(scriptContents, 'utf8')); + } + } else { + workspace.fs.copy(scriptPath, tempScriptPath, { overwrite: true }); + } const fullScript = `node "${tempScriptPath.fsPath}" "${contentConfigFile.fsPath}"`; @@ -187,16 +222,35 @@ export class SsgListener extends BaseListener { } as Field; break; case 'ZodBoolean': - ctField = { - name: field.name, - type: 'boolean' - } as Field; + if (field.name === 'draft') { + ctField = { + name: field.name, + type: 'draft' + } as Field; + } else { + ctField = { + name: field.name, + type: 'boolean' + } as Field; + } break; case 'ZodArray': - ctField = { - name: field.name, - type: 'list' - } as Field; + if (field.name === 'tags') { + ctField = { + name: field.name, + type: 'tags' + } as Field; + } else if (field.name === 'categories') { + ctField = { + name: field.name, + type: 'categories' + } as Field; + } else { + ctField = { + name: field.name, + type: 'list' + } as Field; + } break; case 'ZodEnum': ctField = { @@ -205,12 +259,23 @@ export class SsgListener extends BaseListener { choices: field.options || [] } as Field; break; + case 'datetime': case 'ZodDate': ctField = { name: field.name, type: 'datetime', default: '{{now}}' } as Field; + + if (field.name.toLowerCase() === 'published') { + ctField.isPublishDate = true; + } else if ( + field.name.toLowerCase() === 'modified' || + field.name.toLowerCase() === 'updated' + ) { + ctField.isModifiedDate = true; + } + break; case 'image': ctField = { diff --git a/src/listeners/general/GitListener.ts b/src/listeners/general/GitListener.ts index 342f02fc..e4e76537 100644 --- a/src/listeners/general/GitListener.ts +++ b/src/listeners/general/GitListener.ts @@ -28,6 +28,8 @@ import { import { Folders } from '../../commands/Folders'; import { commands } from 'vscode'; import { PostMessageData } from '../../models'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../../localization'; export class GitListener { private static isRegistered: boolean = false; @@ -177,7 +179,9 @@ export class GitListener { } await subGit.push(); } catch (e) { - Notifications.error(`Failed to push submodules. Please check the logs for more details.`); + Notifications.errorWithOutput( + l10n.t(LocalizationKey.listenersGeneralGitListenerPushError) + ); Logger.error((e as Error).message); return; } @@ -203,7 +207,9 @@ export class GitListener { await git.subModule(['foreach', 'git', 'push']); } } catch (e) { - Notifications.error(`Failed to push submodules. Please check the logs for more details.`); + Notifications.errorWithOutput( + l10n.t(LocalizationKey.listenersGeneralGitListenerPushError) + ); Logger.error((e as Error).message); return; } diff --git a/src/listeners/panel/DataListener.ts b/src/listeners/panel/DataListener.ts index 9cde6738..c985bb09 100644 --- a/src/listeners/panel/DataListener.ts +++ b/src/listeners/panel/DataListener.ts @@ -24,6 +24,8 @@ import { PanelProvider } from '../../panelWebView/PanelProvider'; import { MessageHandlerData } from '@estruyf/vscode'; import { SponsorAi } from '../../services/SponsorAI'; import { Terminal } from '../../services'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../../localization'; const FILE_LIMIT = 10; @@ -94,7 +96,7 @@ export class DataListener extends BaseListener { panel.getWebview()?.postMessage({ command, requestId, - error: 'No active editor' + error: l10n.t(LocalizationKey.listenersPanelDataListenerAiSuggestTaxonomyNoEditorError) } as MessageHandlerData); return; } @@ -104,7 +106,7 @@ export class DataListener extends BaseListener { panel.getWebview()?.postMessage({ command, requestId, - error: 'No article data' + error: l10n.t(LocalizationKey.listenersPanelDataListenerAiSuggestTaxonomyNoDataError) } as MessageHandlerData); return; } @@ -126,7 +128,7 @@ export class DataListener extends BaseListener { panel.getWebview()?.postMessage({ command, requestId, - error: 'No article data' + error: l10n.t(LocalizationKey.listenersPanelDataListenerAiSuggestTaxonomyNoDataError) } as MessageHandlerData); return; } @@ -499,7 +501,11 @@ export class DataListener extends BaseListener { if (entries) { this.sendRequest(command, requestId, entries); } else { - this.sendRequestError(command, requestId, "Couldn't find data file entries"); + this.sendRequestError( + command, + requestId, + l10n.t(LocalizationKey.listenersPanelDataListenerGetDataFileEntriesNoDataFilesError) + ); } } diff --git a/src/listeners/panel/TaxonomyListener.ts b/src/listeners/panel/TaxonomyListener.ts index c6f16a1e..cf6cffd0 100644 --- a/src/listeners/panel/TaxonomyListener.ts +++ b/src/listeners/panel/TaxonomyListener.ts @@ -13,6 +13,8 @@ import { import { SponsorAi } from '../../services/SponsorAI'; import { PanelProvider } from '../../panelWebView/PanelProvider'; import { MessageHandlerData } from '@estruyf/vscode'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../../localization'; export class TaxonomyListener extends BaseListener { /** @@ -80,7 +82,7 @@ export class TaxonomyListener extends BaseListener { panel.getWebview()?.postMessage({ command, requestId, - error: 'No active editor' + error: l10n.t(LocalizationKey.listenersPanelTaxonomyListenerAiSuggestTaxonomyNoDataError) } as MessageHandlerData); return; } @@ -90,7 +92,7 @@ export class TaxonomyListener extends BaseListener { panel.getWebview()?.postMessage({ command, requestId, - error: 'No article data' + error: l10n.t(LocalizationKey.listenersPanelTaxonomyListenerAiSuggestTaxonomyNoEditorError) } as MessageHandlerData); return; } @@ -115,7 +117,7 @@ export class TaxonomyListener extends BaseListener { panel.getWebview()?.postMessage({ command, requestId, - error: 'No article data' + error: l10n.t(LocalizationKey.listenersPanelTaxonomyListenerAiSuggestTaxonomyNoDataError) } as MessageHandlerData); return; } diff --git a/src/localization/localization.enum.ts b/src/localization/localization.enum.ts index 076205b5..d0f5a9b7 100644 --- a/src/localization/localization.enum.ts +++ b/src/localization/localization.enum.ts @@ -115,6 +115,30 @@ export enum LocalizationKey { * No results */ commonNoResults = 'common.noResults', + /** + * Sorry, something went wrong. + */ + commonError = 'common.error', + /** + * yes + */ + commonYes = 'common.yes', + /** + * no + */ + commonNo = 'common.no', + /** + * Open settings + */ + commonOpenSettings = 'common.openSettings', + /** + * output window + */ + notificationsOutputChannelLink = 'notifications.outputChannel.link', + /** + * Check the {0} for more details. + */ + notificationsOutputChannelDescription = 'notifications.outputChannel.description', /** * Common */ @@ -891,6 +915,10 @@ export enum LocalizationKey { * Select a template to prefill the frontmatter.json file with the recommended settings. */ dashboardStepsStepsToGetStartedTemplateDescription = 'dashboard.steps.stepsToGetStarted.template.description', + /** + * Selecting a template applies a whole configuration to your project and closes this configuration view. + */ + dashboardStepsStepsToGetStartedTemplateWarning = 'dashboard.steps.stepsToGetStarted.template.warning', /** * Create Content-Types for your Astro Content Collections */ @@ -1032,7 +1060,7 @@ export enum LocalizationKey { */ dashboardConfigurationAstroAstroContentTypesEmpty = 'dashboard.configuration.astro.astroContentTypes.empty', /** - * The following Astro Content Collections and can be used to generate a content-type. + * The following Astro Content Collections can be used to generate a content-type. */ dashboardConfigurationAstroAstroContentTypesDescription = 'dashboard.configuration.astro.astroContentTypes.description', /** @@ -1452,8 +1480,828 @@ export enum LocalizationKey { * Continue in the media dashboard to select the image you want to insert. */ panelViewPanelMediaInsert = 'panel.viewPanel.mediaInsert', + /** + * No {0} configured. + */ + commandsArticleNotificationNoTaxonomy = 'commands.article.notification.noTaxonomy', + /** + * Select your {0} to insert. + */ + commandsArticleQuickPickPlaceholder = 'commands.article.quickPick.placeholder', + /** + * Something failed while parsing the date format. Check your "{0}" setting. + */ + commandsArticleSetDateError = 'commands.article.setDate.error', + /** + * Failed to rename file: {0} + */ + commandsArticleUpdateSlugError = 'commands.article.updateSlug.error', + /** + * Cache cleared + */ + commandsCacheCleared = 'commands.cache.cleared', + /** + * Ask me anything + */ + commandsChatbotTitle = 'commands.chatbot.title', + /** + * Create content by content type + */ + commandsContentOptionContentTypeLabel = 'commands.content.option.contentType.label', + /** + * Select if you want to create new content by the available content type(s) + */ + commandsContentOptionContentTypeDescription = 'commands.content.option.contentType.description', + /** + * Create content by template + */ + commandsContentOptionTemplateLabel = 'commands.content.option.template.label', + /** + * Select if you want to create new content by the available template(s) + */ + commandsContentOptionTemplateDescription = 'commands.content.option.template.description', + /** + * Create content + */ + commandsContentQuickPickTitle = 'commands.content.quickPick.title', + /** + * Select how you want to create your new content + */ + commandsContentQuickPickPlaceholder = 'commands.content.quickPick.placeholder', + /** + * Dashboard + */ + commandsDashboardTitle = 'commands.dashboard.title', + /** + * Add media folder + */ + commandsFoldersAddMediaFolderInputBoxTitle = 'commands.folders.addMediaFolder.inputBox.title', + /** + * Which name would you like to give to your folder (use "/" to create multi-level folders)? + */ + commandsFoldersAddMediaFolderInputBoxPrompt = 'commands.folders.addMediaFolder.inputBox.prompt', + /** + * No folder name was specified. + */ + commandsFoldersAddMediaFolderNoFolderWarning = 'commands.folders.addMediaFolder.noFolder.warning', + /** + * Folder is already registered + */ + commandsFoldersCreateFolderExistsWarning = 'commands.folders.create.folderExists.warning', + /** + * Register folder + */ + commandsFoldersCreateInputTitle = 'commands.folders.create.input.title', + /** + * Which name would you like to specify for this folder? + */ + commandsFoldersCreateInputPrompt = 'commands.folders.create.input.prompt', + /** + * Folder name + */ + commandsFoldersCreateInputPlaceholder = 'commands.folders.create.input.placeholder', + /** + * Folder registered + */ + commandsFoldersCreateSuccess = 'commands.folders.create.success', + /** + * Please select the main workspace folder for Front Matter to use. + */ + commandsFoldersGetWorkspaceFolderWorkspaceFolderPickPlaceholder = 'commands.folders.getWorkspaceFolder.workspaceFolderPick.placeholder', + /** + * Folder "{0}" does not exist. Please remove it from the settings. + */ + commandsFoldersGetNotificationErrorTitle = 'commands.folders.get.notificationError.title', + /** + * Remove folder + */ + commandsFoldersGetNotificationErrorRemoveAction = 'commands.folders.get.notificationError.remove.action', + /** + * Create folder + */ + commandsFoldersGetNotificationErrorCreateAction = 'commands.folders.get.notificationError.create.action', + /** + * Preview: {0} + */ + commandsPreviewPanelTitle = 'commands.preview.panel.title', + /** + * Select the folder of the article to preview + */ + commandsPreviewAskUserToPickFolderTitle = 'commands.preview.askUserToPickFolder.title', + /** + * Project initialized successfully. + */ + commandsProjectInitializeSuccess = 'commands.project.initialize.success', + /** + * To which project do you want to switch? + */ + commandsProjectSwitchProjectTitle = 'commands.project.switchProject.title', + /** + * Sample template created. + */ + commandsProjectCreateSampleTemplateInfo = 'commands.project.createSampleTemplate.info', + /** + * Insert the value of the {0} that you want to add to your configuration. + */ + commandsSettingsCreateInputPrompt = 'commands.settings.create.input.prompt', + /** + * Name of the {0} + */ + commandsSettingsCreateInputPlaceholder = 'commands.settings.create.input.placeholder', + /** + * The provided {0} already exists. + */ + commandsSettingsCreateWarning = 'commands.settings.create.warning', + /** + * Do you want to add the new {0} to the page? + */ + commandsSettingsCreateQuickPickPlaceholder = 'commands.settings.create.quickPick.placeholder', + /** + * {0}: exporting tags and categories + */ + commandsSettingsExportProgressTitle = 'commands.settings.export.progress.title', + /** + * Export completed. Tags: {0} - Categories: {1}. + */ + commandsSettingsExportProgressSuccess = 'commands.settings.export.progress.success', + /** + * Remap + */ + commandsSettingsRemapQuickpickTitle = 'commands.settings.remap.quickpick.title', + /** + * What do you want to remap? + */ + commandsSettingsRemapQuickpickPlaceholder = 'commands.settings.remap.quickpick.placeholder', + /** + * No {0} configured. + */ + commandsSettingsRemapNoTaxonomyWarning = 'commands.settings.remap.noTaxonomy.warning', + /** + * Select your {0} to insert. + */ + commandsSettingsRemapSelectTaxonomyPlaceholder = 'commands.settings.remap.selectTaxonomy.placeholder', + /** + * Specify the value of the {0} with which you want to remap "{1}". Leave the input if you want to remove the {0} from all articles. + */ + commandsSettingsRemapNewOptionInputPrompt = 'commands.settings.remap.newOption.input.prompt', + /** + * Name of the {0} + */ + commandsSettingsRemapNewOptionInputPlaceholder = 'commands.settings.remap.newOption.input.placeholder', + /** + * Delete {0} {1}? + */ + commandsSettingsRemapDeletePlaceholder = 'commands.settings.remap.delete.placeholder', + /** + * The {0} field is required. Please define a value for the field. + */ + commandsStatusListenerVerifyRequiredFieldsDiagnosticEmptyField = 'commands.statusListener.verifyRequiredFields.diagnostic.emptyField', + /** + * The following fields are required to contain a value: {0} + */ + commandsStatusListenerVerifyRequiredFieldsNotificationError = 'commands.statusListener.verifyRequiredFields.notification.error', + /** + * Template title + */ + commandsTemplateGenerateInputTitle = 'commands.template.generate.input.title', + /** + * Which name would you like to give your template? + */ + commandsTemplateGenerateInputPrompt = 'commands.template.generate.input.prompt', + /** + * article + */ + commandsTemplateGenerateInputPlaceholder = 'commands.template.generate.input.placeholder', + /** + * You did not specify a template title. + */ + commandsTemplateGenerateNoTitleWarning = 'commands.template.generate.noTitle.warning', + /** + * Keep content + */ + commandsTemplateGenerateKeepContentsTitle = 'commands.template.generate.keepContents.title', + /** + * Do you want to keep the contents for the template? + */ + commandsTemplateGenerateKeepContentsPlaceholder = 'commands.template.generate.keepContents.placeholder', + /** + * You did not pick any of the options for keeping the template its content. + */ + commandsTemplateGenerateKeepContentsNoOptionWarning = 'commands.template.generate.keepContents.noOption.warning', + /** + * Template created and is now available in your {0} folder. + */ + commandsTemplateGenerateKeepContentsSuccess = 'commands.template.generate.keepContents.success', + /** + * No templates found. + */ + commandsTemplateGetTemplatesWarning = 'commands.template.getTemplates.warning', + /** + * Incorrect project folder path retrieved. + */ + commandsTemplateCreateFolderPathWarning = 'commands.template.create.folderPath.warning', + /** + * No templates found. + */ + commandsTemplateCreateNoTemplatesWarning = 'commands.template.create.noTemplates.warning', + /** + * Select a template + */ + commandsTemplateCreateSelectTemplateTitle = 'commands.template.create.selectTemplate.title', + /** + * Select the content template to use + */ + commandsTemplateCreateSelectTemplatePlaceholder = 'commands.template.create.selectTemplate.placeholder', + /** + * No template selected. + */ + commandsTemplateCreateSelectTemplateNoTemplateWarning = 'commands.template.create.selectTemplate.noTemplate.warning', + /** + * Content template could not be found. + */ + commandsTemplateCreateSelectTemplateNotFoundWarning = 'commands.template.create.selectTemplate.notFound.warning', + /** + * Your new content is now available. + */ + commandsTemplateCreateSuccess = 'commands.template.create.success', + /** + * Unordered list + */ + commandsWysiwygCommandUnorderedListLabel = 'commands.wysiwyg.command.unorderedList.label', + /** + * Add an unordered list + */ + commandsWysiwygCommandUnorderedListDetail = 'commands.wysiwyg.command.unorderedList.detail', + /** + * Ordered list + */ + commandsWysiwygCommandOrderedListLabel = 'commands.wysiwyg.command.orderedList.label', + /** + * Add an ordered list + */ + commandsWysiwygCommandOrderedListDetail = 'commands.wysiwyg.command.orderedList.detail', + /** + * Task list + */ + commandsWysiwygCommandTaskListLabel = 'commands.wysiwyg.command.taskList.label', + /** + * Add a task list + */ + commandsWysiwygCommandTaskListDetail = 'commands.wysiwyg.command.taskList.detail', + /** + * Code + */ + commandsWysiwygCommandCodeLabel = 'commands.wysiwyg.command.code.label', + /** + * Add inline code snippet + */ + commandsWysiwygCommandCodeDetail = 'commands.wysiwyg.command.code.detail', + /** + * Code block + */ + commandsWysiwygCommandCodeblockLabel = 'commands.wysiwyg.command.codeblock.label', + /** + * Add a code block + */ + commandsWysiwygCommandCodeblockDetail = 'commands.wysiwyg.command.codeblock.detail', + /** + * Blockquote + */ + commandsWysiwygCommandBlockquoteLabel = 'commands.wysiwyg.command.blockquote.label', + /** + * Add a blockquote + */ + commandsWysiwygCommandBlockquoteDetail = 'commands.wysiwyg.command.blockquote.detail', + /** + * Strikethrough + */ + commandsWysiwygCommandStrikethroughLabel = 'commands.wysiwyg.command.strikethrough.label', + /** + * Add strikethrough text + */ + commandsWysiwygCommandStrikethroughDetail = 'commands.wysiwyg.command.strikethrough.detail', + /** + * WYSIWYG Options + */ + commandsWysiwygQuickPickTitle = 'commands.wysiwyg.quickPick.title', + /** + * Which type of markup would you like to insert? + */ + commandsWysiwygQuickPickPlaceholder = 'commands.wysiwyg.quickPick.placeholder', + /** + * WYSIWYG Hyperlink + */ + commandsWysiwygAddHyperlinkHyperlinkInputTitle = 'commands.wysiwyg.addHyperlink.hyperlinkInput.title', + /** + * Enter the URL + */ + commandsWysiwygAddHyperlinkHyperlinkInputPrompt = 'commands.wysiwyg.addHyperlink.hyperlinkInput.prompt', + /** + * WYSIWYG Text + */ + commandsWysiwygAddHyperlinkTextInputTitle = 'commands.wysiwyg.addHyperlink.textInput.title', + /** + * Enter the text for the hyperlink + */ + commandsWysiwygAddHyperlinkTextInputPrompt = 'commands.wysiwyg.addHyperlink.textInput.prompt', + /** + * Heading level + */ + commandsWysiwygInsertTextHeadingInputTitle = 'commands.wysiwyg.insertText.heading.input.title', + /** + * Which heading level do you want to insert? + */ + commandsWysiwygInsertTextHeadingInputPlaceholder = 'commands.wysiwyg.insertText.heading.input.placeholder', + /** + * A page bundle with the name {0} already exists in {1}. + */ + helpersArticleHelperCreateContentPageBundleError = 'helpers.articleHelper.createContent.pageBundle.error', + /** + * Content with the title already exists. Please specify a new title. + */ + helpersArticleHelperCreateContentContentExistsWarning = 'helpers.articleHelper.createContent.contentExists.warning', + /** + * Error while processing the {0} placeholder. + */ + helpersArticleHelperProcessCustomPlaceholdersPlaceholderError = 'helpers.articleHelper.processCustomPlaceholders.placeholder.error', + /** + * Error parsing the front matter of {0}. + */ + helpersArticleHelperParseFileDiagnosticError = 'helpers.articleHelper.parseFile.diagnostic.error', + /** + * No front matter data found to generate a content type. + */ + helpersContentTypeGenerateNoFrontMatterError = 'helpers.contentType.generate.noFrontMatter.error', + /** + * Override the default content type + */ + helpersContentTypeGenerateOverrideQuickPickTitle = 'helpers.contentType.generate.override.quickPick.title', + /** + * Do you want to overwrite the default content type configuration with the fields used in the current field? + */ + helpersContentTypeGenerateOverrideQuickPickPlaceholder = 'helpers.contentType.generate.override.quickPick.placeholder', + /** + * Generate Content Type + */ + helpersContentTypeGenerateContentTypeInputTitle = 'helpers.contentType.generate.contentTypeInput.title', + /** + * Enter the name of the content type to generate + */ + helpersContentTypeGenerateContentTypeInputPrompt = 'helpers.contentType.generate.contentTypeInput.prompt', + /** + * Please enter a name for the content type. + */ + helpersContentTypeGenerateContentTypeInputValidationEnterName = 'helpers.contentType.generate.contentTypeInput.validation.enterName', + /** + * A content type with this name already exists. + */ + helpersContentTypeGenerateContentTypeInputValidationNameExists = 'helpers.contentType.generate.contentTypeInput.validation.nameExists', + /** + * You didn't specify a name for the content type. + */ + helpersContentTypeGenerateNoContentTypeNameWarning = 'helpers.contentType.generate.noContentTypeName.warning', + /** + * Use as a page bundle + */ + helpersContentTypeGeneratePageBundleQuickPickTitle = 'helpers.contentType.generate.pageBundle.quickPick.title', + /** + * Do you want to use this content type as a page bundle? + */ + helpersContentTypeGeneratePageBundleQuickPickPlaceHolder = 'helpers.contentType.generate.pageBundle.quickPick.placeHolder', + /** + * Content type {0} has been updated. + */ + helpersContentTypeGenerateUpdatedSuccess = 'helpers.contentType.generate.updated.success', + /** + * Content type {0} has been generated. + */ + helpersContentTypeGenerateGeneratedSuccess = 'helpers.contentType.generate.generated.success', + /** + * No front matter data found to add missing fields. + */ + helpersContentTypeAddMissingFieldsNoFrontMatterWarning = 'helpers.contentType.addMissingFields.noFrontMatter.warning', + /** + * Content type {0} has been updated. + */ + helpersContentTypeAddMissingFieldsUpdatedSuccess = 'helpers.contentType.addMissingFields.updated.success', + /** + * No front matter data found to set the content type. + */ + helpersContentTypeSetContentTypeNoFrontMatterWarning = 'helpers.contentType.setContentType.noFrontMatter.warning', + /** + * Select the content type + */ + helpersContentTypeSetContentTypeQuickPickTitle = 'helpers.contentType.setContentType.quickPick.title', + /** + * Which content type would you like to use? + */ + helpersContentTypeSetContentTypeQuickPickPlaceholder = 'helpers.contentType.setContentType.quickPick.placeholder', + /** + * {0}: Creating content... + */ + helpersContentTypeCreateProgressTitle = 'helpers.contentType.create.progress.title', + /** + * Your new content has been created. + */ + helpersContentTypeCreateSuccess = 'helpers.contentType.create.success', + /** + * The content type actions are not available in this mode. + */ + helpersContentTypeVerifyWarning = 'helpers.contentType.verify.warning', + /** + * Executing: {0} + */ + helpersCustomScriptExecuting = 'helpers.customScript.executing', + /** + * {0}: Article couldn't be retrieved. + */ + helpersCustomScriptSingleRunArticleWarning = 'helpers.customScript.singleRun.article.warning', + /** + * {0}: No files found + */ + helpersCustomScriptBulkRunNoFilesWarning = 'helpers.customScript.bulkRun.noFiles.warning', + /** + * {0}: There was no folder or media path specified. + */ + helpersCustomScriptRunMediaScriptNoFolderWarning = 'helpers.customScript.runMediaScript.noFolder.warning', + /** + * {0}: front matter updated. + */ + helpersCustomScriptShowOutputFrontMatterSuccess = 'helpers.customScript.showOutput.frontMatter.success', + /** + * Copy output + */ + helpersCustomScriptShowOutputCopyOutputAction = 'helpers.customScript.showOutput.copyOutput.action', + /** + * {0}: Executed your custom script. + */ + helpersCustomScriptShowOutputSuccess = 'helpers.customScript.showOutput.success', + /** + * Invalid command: {0} + */ + helpersCustomScriptValidateCommandError = 'helpers.customScript.validateCommand.error', + /** + * Something went wrong while processing the data file. + */ + helpersDataFileHelperProcessError = 'helpers.dataFileHelper.process.error', + /** + * Check the changelog + */ + helpersExtensionGetVersionChangelog = 'helpers.extension.getVersion.changelog', + /** + * Give it a ⭐️ + */ + helpersExtensionGetVersionStarIt = 'helpers.extension.getVersion.starIt', + /** + * {0} has been updated to v{1} β€” check out what's new! + */ + helpersExtensionGetVersionUpdateNotification = 'helpers.extension.getVersion.update.notification', + /** + * The "{0}" and "{1}" settings have been deprecated. Please use the "isPublishDate" and "isModifiedDate" datetime field properties instead. + */ + helpersExtensionMigrateSettingsDeprecatedWarning = 'helpers.extension.migrateSettings.deprecated.warning', + /** + * Hide + */ + helpersExtensionMigrateSettingsDeprecatedWarningHide = 'helpers.extension.migrateSettings.deprecated.warning.hide', + /** + * See migration guide + */ + helpersExtensionMigrateSettingsDeprecatedWarningSeeGuide = 'helpers.extension.migrateSettings.deprecated.warning.seeGuide', + /** + * {0} - Templates + */ + helpersExtensionMigrateSettingsTemplatesQuickPickTitle = 'helpers.extension.migrateSettings.templates.quickPick.title', + /** + * Do you want to keep on using the template functionality? + */ + helpersExtensionMigrateSettingsTemplatesQuickPickPlaceholder = 'helpers.extension.migrateSettings.templates.quickPick.placeholder', + /** + * Front Matter BETA cannot be used while the stable version is installed. Please ensure that you have only over version installed. + */ + helpersExtensionCheckIfExtensionCanRunWarning = 'helpers.extension.checkIfExtensionCanRun.warning', + /** + * We couldn't find your selected folder. + */ + helpersMediaHelperSaveFileFolderError = 'helpers.mediaHelper.saveFile.folder.error', + /** + * File {0} uploaded to: {1} + */ + helpersMediaHelperSaveFileFileUploadedSuccess = 'helpers.mediaHelper.saveFile.file.uploaded.success', + /** + * Sorry, something went wrong uploading {0} + */ + helpersMediaHelperSaveFileFileUploadedFailed = 'helpers.mediaHelper.saveFile.file.uploaded.failed', + /** + * Sorry, something went wrong deleting {0} + */ + helpersMediaHelperDeleteFileFileDeletionFailed = 'helpers.mediaHelper.deleteFile.file.deletion.failed', + /** + * The name "{0}" already exists at the file location. + */ + helpersMediaLibraryRemoveWarning = 'helpers.mediaLibrary.remove.warning', + /** + * Sorry, something went wrong updating "{0}" to "{1}". + */ + helpersMediaLibraryRemoveError = 'helpers.mediaLibrary.remove.error', + /** + * Couldn't open the file. + */ + helpersOpenFileInEditorError = 'helpers.openFileInEditor.error', + /** + * Title or description + */ + helpersQuestionsContentTitleAiInputTitle = 'helpers.questions.contentTitle.aiInput.title', + /** + * What would you like to write about? + */ + helpersQuestionsContentTitleAiInputPrompt = 'helpers.questions.contentTitle.aiInput.prompt', + /** + * What would you like to write about? + */ + helpersQuestionsContentTitleAiInputPlaceholder = 'helpers.questions.contentTitle.aiInput.placeholder', + /** + * your title/description + */ + helpersQuestionsContentTitleAiInputQuickPickTitleSeparator = 'helpers.questions.contentTitle.aiInput.quickPick.title.separator', + /** + * AI generated title + */ + helpersQuestionsContentTitleAiInputQuickPickAiSeparator = 'helpers.questions.contentTitle.aiInput.quickPick.ai.separator', + /** + * Select a title + */ + helpersQuestionsContentTitleAiInputSelectTitle = 'helpers.questions.contentTitle.aiInput.select.title', + /** + * Select a title for your content + */ + helpersQuestionsContentTitleAiInputSelectPlaceholder = 'helpers.questions.contentTitle.aiInput.select.placeholder', + /** + * Failed fetching the AI title. Please try to use your own title or try again later. + */ + helpersQuestionsContentTitleAiInputFailed = 'helpers.questions.contentTitle.aiInput.failed', + /** + * You did not specify a title for your content. + */ + helpersQuestionsContentTitleAiInputWarning = 'helpers.questions.contentTitle.aiInput.warning', + /** + * Content title + */ + helpersQuestionsContentTitleTitleInputTitle = 'helpers.questions.contentTitle.titleInput.title', + /** + * What would you like to use as a title for the content to create? + */ + helpersQuestionsContentTitleTitleInputPrompt = 'helpers.questions.contentTitle.titleInput.prompt', + /** + * Content title + */ + helpersQuestionsContentTitleTitleInputPlaceholder = 'helpers.questions.contentTitle.titleInput.placeholder', + /** + * You did not specify a title for your content. + */ + helpersQuestionsContentTitleTitleInputWarning = 'helpers.questions.contentTitle.titleInput.warning', + /** + * Select a folder + */ + helpersQuestionsSelectContentFolderQuickPickTitle = 'helpers.questions.selectContentFolder.quickPick.title', + /** + * Select where you want to create your content + */ + helpersQuestionsSelectContentFolderQuickPickPlaceholder = 'helpers.questions.selectContentFolder.quickPick.placeholder', + /** + * No page folders were configured. + */ + helpersQuestionsSelectContentFolderQuickPickNoFoldersWarning = 'helpers.questions.selectContentFolder.quickPick.noFolders.warning', + /** + * You didn't select a place where you wanted to create your content. + */ + helpersQuestionsSelectContentFolderQuickPickNoSelectionWarning = 'helpers.questions.selectContentFolder.quickPick.noSelection.warning', + /** + * No content types found. Please create a content type first. + */ + helpersQuestionsSelectContentTypeNoContentTypeWarning = 'helpers.questions.selectContentType.noContentType.warning', + /** + * Content type + */ + helpersQuestionsSelectContentTypeQuickPickTitle = 'helpers.questions.selectContentType.quickPick.title', + /** + * Select the content type to create your new content + */ + helpersQuestionsSelectContentTypeQuickPickPlaceholder = 'helpers.questions.selectContentType.quickPick.placeholder', + /** + * No content type was selected. + */ + helpersQuestionsSelectContentTypeNoSelectionWarning = 'helpers.questions.selectContentType.noSelection.warning', + /** + * Article {0} is longer than {1} characters (current length: {2}). For SEO reasons, it would be better to make it less than {1} characters. + */ + helpersSeoHelperCheckLengthDiagnosticMessage = 'helpers.seoHelper.checkLength.diagnostic.message', + /** + * You have local settings. Would you like to promote them to the global settings ("frontmatter.json")? + */ + helpersSettingsHelperCheckToPromoteMessage = 'helpers.settingsHelper.checkToPromote.message', + /** + * All settings promoted to team level. + */ + helpersSettingsHelperPromoteSuccess = 'helpers.settingsHelper.promote.success', + /** + * {0}: Reading dynamic config file... + */ + helpersSettingsHelperReadConfigProgressTitle = 'helpers.settingsHelper.readConfig.progress.title', + /** + * Error reading your configuration. + */ + helpersSettingsHelperReadConfigError = 'helpers.settingsHelper.readConfig.error', + /** + * Settings have been refreshed. + */ + helpersSettingsHelperRefreshConfigSuccess = 'helpers.settingsHelper.refreshConfig.success', + /** + * Rename the {0} + */ + helpersTaxonomyHelperRenameInputTitle = 'helpers.taxonomyHelper.rename.input.title', + /** + * The new value must be different from the old one. + */ + helpersTaxonomyHelperRenameValidateEqualValue = 'helpers.taxonomyHelper.rename.validate.equalValue', + /** + * A new value must be provided. + */ + helpersTaxonomyHelperRenameValidateNoValue = 'helpers.taxonomyHelper.rename.validate.noValue', + /** + * Merge the "{0}" with another {1} value + */ + helpersTaxonomyHelperMergeQuickPickTitle = 'helpers.taxonomyHelper.merge.quickPick.title', + /** + * Select the {0} value to merge with + */ + helpersTaxonomyHelperMergeQuickPickPlaceholder = 'helpers.taxonomyHelper.merge.quickPick.placeholder', + /** + * Delete the "{0}" {1} value + */ + helpersTaxonomyHelperDeleteQuickPickTitle = 'helpers.taxonomyHelper.delete.quickPick.title', + /** + * Are you sure you want to delete the "{0}" {1} value? + */ + helpersTaxonomyHelperDeleteQuickPickPlaceholder = 'helpers.taxonomyHelper.delete.quickPick.placeholder', + /** + * Create a new {0} value + */ + helpersTaxonomyHelperCreateNewInputTitle = 'helpers.taxonomyHelper.createNew.input.title', + /** + * Enter the value you want to add + */ + helpersTaxonomyHelperCreateNewInputPlaceholder = 'helpers.taxonomyHelper.createNew.input.placeholder', + /** + * A value must be provided. + */ + helpersTaxonomyHelperCreateNewInputValidateNoValue = 'helpers.taxonomyHelper.createNew.input.validate.noValue', + /** + * The value already exists. + */ + helpersTaxonomyHelperCreateNewInputValidateExists = 'helpers.taxonomyHelper.createNew.input.validate.exists', + /** + * {0}: Renaming "{1}" from {2} to {3}. + */ + helpersTaxonomyHelperProcessEdit = 'helpers.taxonomyHelper.process.edit', + /** + * {0}: Merging "{1}" from {2} to {3}. + */ + helpersTaxonomyHelperProcessMerge = 'helpers.taxonomyHelper.process.merge', + /** + * {0}: Deleting "{1}" from {2}. + */ + helpersTaxonomyHelperProcessDelete = 'helpers.taxonomyHelper.process.delete', + /** + * Edit completed. + */ + helpersTaxonomyHelperProcessEditSuccess = 'helpers.taxonomyHelper.process.edit.success', + /** + * Merge completed. + */ + helpersTaxonomyHelperProcessMergeSuccess = 'helpers.taxonomyHelper.process.merge.success', + /** + * Deletion completed. + */ + helpersTaxonomyHelperProcessDeleteSuccess = 'helpers.taxonomyHelper.process.delete.success', + /** + * Move the "{0}" to another type + */ + helpersTaxonomyHelperMoveQuickPickTitle = 'helpers.taxonomyHelper.move.quickPick.title', + /** + * Select the type to move to + */ + helpersTaxonomyHelperMoveQuickPickPlaceholder = 'helpers.taxonomyHelper.move.quickPick.placeholder', + /** + * {0}: Moving "{1}" from {2} to "${3}". + */ + helpersTaxonomyHelperMoveProgressTitle = 'helpers.taxonomyHelper.move.progress.title', + /** + * Move completed. + */ + helpersTaxonomyHelperMoveSuccess = 'helpers.taxonomyHelper.move.success', + /** + * Open the "frontmatter.json" file if you want to review the configuration. + */ + listenersDashboardDashboardListenerOpenConfigNotification = 'listeners.dashboard.dashboardListener.openConfig.notification', + /** + * No path provided. + */ + listenersDashboardDashboardListenerPinItemNoPathError = 'listeners.dashboard.dashboardListener.pinItem.noPath.error', + /** + * Could not pin item. + */ + listenersDashboardDashboardListenerPinItemCoundNotPinError = 'listeners.dashboard.dashboardListener.pinItem.coundNotPin.error', + /** + * Could not unpin item. + */ + listenersDashboardDashboardListenerPinItemCoundNotUnPinError = 'listeners.dashboard.dashboardListener.pinItem.coundNotUnPin.error', /** * Template files copied. */ - listenersDashboardSettingsListenerTriggerTemplateNotification = 'listeners.dashboard.settingsListener.triggerTemplate.notification' + listenersDashboardSettingsListenerTriggerTemplateNotification = 'listeners.dashboard.settingsListener.triggerTemplate.notification', + /** + * Downloading and initializing the template... + */ + listenersDashboardSettingsListenerTriggerTemplateProgressTitle = 'listeners.dashboard.settingsListener.triggerTemplate.progress.title', + /** + * Failed to download the template. + */ + listenersDashboardSettingsListenerTriggerTemplateDownloadError = 'listeners.dashboard.settingsListener.triggerTemplate.download.error', + /** + * Failed to initialize the template. + */ + listenersDashboardSettingsListenerTriggerTemplateInitError = 'listeners.dashboard.settingsListener.triggerTemplate.init.error', + /** + * Snippet missing title or body + */ + listenersDashboardSnippetListenerAddSnippetMissingFieldsWarning = 'listeners.dashboard.snippetListener.addSnippet.missingFields.warning', + /** + * Snippet with the same title already exists + */ + listenersDashboardSnippetListenerAddSnippetExistsWarning = 'listeners.dashboard.snippetListener.addSnippet.exists.warning', + /** + * No snippets to update + */ + listenersDashboardSnippetListenerUpdateSnippetNoSnippetsWarning = 'listeners.dashboard.snippetListener.updateSnippet.noSnippets.warning', + /** + * Failed to push submodules. + */ + listenersGeneralGitListenerPushError = 'listeners.general.gitListener.push.error', + /** + * No active editor + */ + listenersPanelDataListenerAiSuggestTaxonomyNoEditorError = 'listeners.panel.dataListener.aiSuggestTaxonomy.noEditor.error', + /** + * No article data + */ + listenersPanelDataListenerAiSuggestTaxonomyNoDataError = 'listeners.panel.dataListener.aiSuggestTaxonomy.noData.error', + /** + * Couldn't find data file entries + */ + listenersPanelDataListenerGetDataFileEntriesNoDataFilesError = 'listeners.panel.dataListener.getDataFileEntries.noDataFiles.error', + /** + * No active editor + */ + listenersPanelTaxonomyListenerAiSuggestTaxonomyNoEditorError = 'listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error', + /** + * No article data + */ + listenersPanelTaxonomyListenerAiSuggestTaxonomyNoDataError = 'listeners.panel.taxonomyListener.aiSuggestTaxonomy.noData.error', + /** + * Select the mode you want to use + */ + servicesModeSwitchSwitchModeQuickPickPlaceholder = 'services.modeSwitch.switchMode.quickPick.placeholder', + /** + * {0}: Mode selection + */ + servicesModeSwitchSwitchModeQuickPickTitle = 'services.modeSwitch.switchMode.quickPick.title', + /** + * Mode: {0} + */ + servicesModeSwitchSetTextMode = 'services.modeSwitch.setText.mode', + /** + * Processing... + */ + servicesPagesParserParsePagesStatusBarText = 'services.pagesParser.parsePages.statusBar.text', + /** + * File error: {0} + */ + servicesPagesParserParsePagesFileError = 'services.pagesParser.parsePages.file.error', + /** + * The AI title generation took too long. Please try again later. + */ + servicesSponsorAiGetTitlesWarning = 'services.sponsorAi.getTitles.warning', + /** + * The AI description generation took too long. Please try again later. + */ + servicesSponsorAiGetDescriptionWarning = 'services.sponsorAi.getDescription.warning', + /** + * The AI taxonomy generation took too long. Please try again later. + */ + servicesSponsorAiGetTaxonomySuggestionsWarning = 'services.sponsorAi.getTaxonomySuggestions.warning', + /** + * Starting local server + */ + servicesTerminalOpenLocalServerTerminalTerminalOptionMessage = 'services.terminal.openLocalServerTerminal.terminalOption.message' } diff --git a/src/models/AstroCollections.ts b/src/models/AstroCollections.ts index f07b9c3f..b91fcc75 100644 --- a/src/models/AstroCollections.ts +++ b/src/models/AstroCollections.ts @@ -14,6 +14,7 @@ export interface AstroField { | 'ZodEnum' | 'ZodDate' | 'ZodObject' + | 'datetime' | 'email' | 'url' | 'image'; diff --git a/src/providers/MarkdownFoldingProvider.ts b/src/providers/MarkdownFoldingProvider.ts index 8ea5656f..1811a8ee 100644 --- a/src/providers/MarkdownFoldingProvider.ts +++ b/src/providers/MarkdownFoldingProvider.ts @@ -18,8 +18,7 @@ import { } from 'vscode'; import { SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, - SETTING_CONTENT_SUPPORTED_FILETYPES, - SETTING_FRONTMATTER_TYPE + SETTING_CONTENT_SUPPORTED_FILETYPES } from '../constants'; import { Settings } from '../helpers'; import { FrontMatterDecorationProvider } from './FrontMatterDecorationProvider'; diff --git a/src/services/ModeSwitch.ts b/src/services/ModeSwitch.ts index aeaef0e6..6314b180 100644 --- a/src/services/ModeSwitch.ts +++ b/src/services/ModeSwitch.ts @@ -1,9 +1,15 @@ import { ModeListener } from './../listeners/general/ModeListener'; -import { SETTING_GLOBAL_ACTIVE_MODE, SETTING_GLOBAL_MODES } from './../constants/settings'; -import { commands, StatusBarAlignment, StatusBarItem, ThemeColor, window } from 'vscode'; +import { + EXTENSION_NAME, + SETTING_GLOBAL_ACTIVE_MODE, + SETTING_GLOBAL_MODES +} from './../constants/settings'; +import { commands, StatusBarAlignment, StatusBarItem, window } from 'vscode'; import { Settings } from '../helpers/SettingsHelper'; import { COMMAND_NAME, CONTEXT } from '../constants'; import { Mode } from '../models'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class ModeSwitch { private static isInit: boolean = false; @@ -56,9 +62,9 @@ export class ModeSwitch { const modePicks = ['Default', ...modes.map((m) => m.id)]; const mode = await window.showQuickPick(modePicks, { - placeHolder: `Select the mode you want to use`, - ignoreFocusOut: true, - title: `Front Matter: Mode selection` + title: l10n.t(LocalizationKey.servicesModeSwitchSwitchModeQuickPickTitle, EXTENSION_NAME), + placeHolder: l10n.t(LocalizationKey.servicesModeSwitchSwitchModeQuickPickPlaceholder), + ignoreFocusOut: true }); if (mode) { @@ -70,9 +76,10 @@ export class ModeSwitch { } private static setText() { - ModeSwitch.statusBarElm.text = `$(preview) Mode: ${ + ModeSwitch.statusBarElm.text = `$(preview) ${l10n.t( + LocalizationKey.servicesModeSwitchSetTextMode, ModeSwitch.currentMode ? ModeSwitch.currentMode : 'Default' - }`; + )}`; ModeSwitch.statusBarElm.show(); } } diff --git a/src/services/PagesParser.ts b/src/services/PagesParser.ts index d3168a02..57ef1552 100644 --- a/src/services/PagesParser.ts +++ b/src/services/PagesParser.ts @@ -26,6 +26,8 @@ import { } from '../helpers'; import { existsAsync } from '../utils'; import { Article, Cache } from '../commands'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; export class PagesParser { public static allPages: Page[] = []; @@ -81,7 +83,9 @@ export class PagesParser { const pages: Page[] = []; if (folderInfo) { - PagesParser.pagesStatusBar.text = '$(sync~spin) Processing pages...'; + PagesParser.pagesStatusBar.text = `$(sync~spin) ${l10n.t( + LocalizationKey.servicesPagesParserParsePagesStatusBarText + )}`; PagesParser.pagesStatusBar.show(); for (const folder of folderInfo) { @@ -108,7 +112,12 @@ export class PagesParser { } Logger.error(`PagesParser::parsePages: ${file.filePath} - ${error.message}`); - Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`); + Notifications.error( + l10n.t( + LocalizationKey.servicesPagesParserParsePagesFileError, + `${file.filePath} - ${error?.message || error}` + ) + ); } } } diff --git a/src/services/SponsorAI.ts b/src/services/SponsorAI.ts index bd8b4b35..0fb06db9 100644 --- a/src/services/SponsorAI.ts +++ b/src/services/SponsorAI.ts @@ -3,6 +3,8 @@ import { Logger, Notifications, Settings, TaxonomyHelper } from '../helpers'; import fetch from 'node-fetch'; import { TagType } from '../panelWebView/TagType'; import { TaxonomyType } from '../models'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; const AI_URL = 'https://frontmatter.codes/api/ai'; // const AI_URL = 'http://localhost:3000/api/ai'; @@ -18,7 +20,7 @@ export class SponsorAi { try { const controller = new AbortController(); const timeout = setTimeout(() => { - Notifications.warning(`The AI title generation took too long. Please try again later.`); + Notifications.warning(l10n.t(LocalizationKey.servicesSponsorAiGetTitlesWarning)); controller.abort(); }, 10000); const signal = controller.signal; @@ -51,7 +53,7 @@ export class SponsorAi { try { const controller = new AbortController(); const timeout = setTimeout(() => { - Notifications.warning(`The AI title generation took too long. Please try again later.`); + Notifications.warning(l10n.t(LocalizationKey.servicesSponsorAiGetDescriptionWarning)); controller.abort(); }, 10000); const signal = controller.signal; @@ -103,7 +105,9 @@ export class SponsorAi { try { const controller = new AbortController(); const timeout = setTimeout(() => { - Notifications.warning(`The AI taxonomy generation took too long. Please try again later.`); + Notifications.warning( + l10n.t(LocalizationKey.servicesSponsorAiGetTaxonomySuggestionsWarning) + ); controller.abort(); }, 10000); const signal = controller.signal; diff --git a/src/services/Terminal.ts b/src/services/Terminal.ts index 7b5c4ee3..ae17710d 100644 --- a/src/services/Terminal.ts +++ b/src/services/Terminal.ts @@ -1,6 +1,8 @@ import { workspace, window, ThemeIcon, TerminalOptions } from 'vscode'; import * as os from 'os'; import { Folders } from '../commands'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../localization'; interface ShellSetting { path: string; @@ -45,7 +47,9 @@ export class Terminal { const terminalOptions: TerminalOptions = { name: Terminal.terminalName, iconPath: new ThemeIcon('server-environment'), - message: `Starting local server` + message: l10n.t( + LocalizationKey.servicesTerminalOpenLocalServerTerminalTerminalOptionMessage + ) }; // Check if workspace diff --git a/ssg-scripts/astro.collections.mjs b/ssg-scripts/astro.collections.mjs index 7603d2f8..717270c6 100644 --- a/ssg-scripts/astro.collections.mjs +++ b/ssg-scripts/astro.collections.mjs @@ -1,32 +1,35 @@ -import { writeFileSync } from "fs"; -import { join } from "path"; -import { createServer } from "vite"; -import zod from "zod"; +import { writeFileSync } from 'fs'; +import { join } from 'path'; +import { createServer } from 'vite'; +import zod from 'astro/zod'; -const { - ZodDefault, - ZodObject, - ZodOptional, - ZodString, - ZodEffects, - ZodEnum -} = zod; +const { ZodDefault, ZodObject, ZodOptional, ZodString, ZodEffects, ZodEnum, ZodUnion } = zod; /** * Process the Zod field - * @param {ZodTypeAny} field - * @param {string} defaultValue - * @returns + * @param {ZodTypeAny} field + * @param {boolean} isOptional + * @param {string} defaultValue + * @returns */ -function getField(field, defaultValue = undefined) { - let isOptional = false; - +function getField(field, isOptional = false, defaultValue = undefined) { // Handle various type transformations and assignments if (field instanceof ZodOptional) { isOptional = true; const type = field.unwrap(); - return getField(type, isOptional, defaultValue) + return getField(type, isOptional, defaultValue); + } + + if (field instanceof ZodEffects) { + const type = field.sourceType(); + return getField(type, isOptional, defaultValue); + } + + if (field instanceof ZodUnion) { + const types = field._def.options; + const type = types[0]; + return getField(type, isOptional, defaultValue); } if (field instanceof ZodDefault) { @@ -35,43 +38,43 @@ function getField(field, defaultValue = undefined) { defaultValue = field.parse(undefined); // https://github.com/sachinraja/zod-to-ts/blob/main/src/index.ts const type = field._def.innerType; - return getField(type, isOptional, defaultValue) + return getField(type, isOptional, defaultValue); } return { type: field, isOptional, defaultValue - } + }; } /** * Generate the field information - * @param {string} name - * @param {ZodTypeAny} type - * @returns + * @param {string} name + * @param {ZodTypeAny} type + * @returns */ function generateFieldInfo(name, type) { let description = type.description; let defaultValue = undefined; - const { - type: fieldType, - isOptional: isFieldOptional, - defaultValue: fieldDefaultValue - } = getField(type, defaultValue); + const { + type: fieldType, + isOptional: isFieldOptional, + defaultValue: fieldDefaultValue + } = getField(type, false, defaultValue); const fieldInfo = { name: name, description: description, defaultValue: fieldDefaultValue, type: fieldType._def.typeName, - required: !isFieldOptional, + required: !isFieldOptional }; if (fieldType instanceof ZodObject) { const subFields = extractFieldInfoFromShape(fieldType); - + fieldInfo.fields = subFields; } @@ -79,6 +82,10 @@ function generateFieldInfo(name, type) { fieldInfo.type = fieldType.sourceType().fmFieldType; } + if (fieldInfo.name.toLowerCase().includes('image')) { + fieldInfo.type = 'image'; + } + if (fieldType instanceof ZodEnum) { fieldInfo.options = fieldType.options; } @@ -96,8 +103,8 @@ function generateFieldInfo(name, type) { /** * Parse the scheme into an array of fields - * @param {ZodTypeAny} type - * @returns + * @param {ZodTypeAny} type + * @returns */ function extractFieldInfoFromShape(type) { if (type instanceof ZodOptional) { @@ -106,7 +113,7 @@ function extractFieldInfoFromShape(type) { if (!(type instanceof ZodObject)) { // Return an empty array if the type is not of the expected type - return []; + return []; } // Iterate through the shape properties @@ -121,8 +128,8 @@ function extractFieldInfoFromShape(type) { /** * Process each content collection - * @param {*} collections - * @returns + * @param {*} collections + * @returns */ function processCollection(collections) { if (!Array.isArray(collections)) { @@ -130,32 +137,35 @@ function processCollection(collections) { } return collections.map(([name, collection]) => { - const schema = typeof collection.schema === "function" ? collection.schema({ - image() { - const field = zod.string(); - field.fmFieldType = "image"; - return field; - } - }) : collection.schema; + const schema = + typeof collection.schema === 'function' + ? collection.schema({ + image() { + const field = zod.string(); + field.fmFieldType = 'image'; + return field; + } + }) + : collection.schema; return { name, - type: collection.type || "content", - fields: extractFieldInfoFromShape(schema), + type: collection.type || 'content', + fields: extractFieldInfoFromShape(schema) }; }); } /** * More info: https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention - * @returns + * @returns */ const astroContentModulePlugin = () => { - const astroContent = "astro:content"; + const astroContent = 'astro:content'; const astroContentMarker = `\0${astroContent}`; return { - name: "astro-content-collections", + name: 'astro-content-collections', resolveId(importee) { if (importee === astroContent) { return astroContentMarker; @@ -174,7 +184,7 @@ const astroContentModulePlugin = () => { `; } } - } + }; }; /** @@ -182,8 +192,8 @@ const astroContentModulePlugin = () => { */ (async () => { const configPath = process.argv[2]; - if (!configPath || typeof configPath !== "string") { - console.log("No config path provided"); + if (!configPath || typeof configPath !== 'string') { + console.log('No config path provided'); process.exit(1); } @@ -195,22 +205,25 @@ const astroContentModulePlugin = () => { hmr: false }, optimizeDeps: { - disabled: true, + disabled: true }, - appType: "custom", + appType: 'custom', plugins: [astroContentModulePlugin()] }); try { // https://github.dev/withastro/astro/blob/defab70cb2a0c67d5e9153542490d2749046b151/packages/astro/src/content/utils.ts#L334 - const contentModule = await server.ssrLoadModule(configPath) + const contentModule = await server.ssrLoadModule(configPath); const collections = Object.entries(contentModule.collections ?? {}); const processedCollections = processCollection(collections); - writeFileSync(join(process.cwd(), `./.frontmatter/temp/astro.collections.json`), JSON.stringify(processedCollections)); - - console.log("Collections generated successfully"); + writeFileSync( + join(process.cwd(), `./.frontmatter/temp/astro.collections.json`), + JSON.stringify(processedCollections, null, 2) + ); + + console.log('Collections generated successfully'); process.exit(0); } catch (error) { console.log(error.message); @@ -218,4 +231,4 @@ const astroContentModulePlugin = () => { } finally { await server.close(); } -})(); \ No newline at end of file +})();