#756 - Support the ability to use Deepl

This commit is contained in:
Elio Struyf
2024-02-20 21:59:36 +01:00
parent cc375801c2
commit 49e7fe6377
7 changed files with 283 additions and 156 deletions
+1
View File
@@ -537,6 +537,7 @@
"commands.i18n.create.success.created": "Created \"{0}\" i18n content file.",
"commands.i18n.create.quickPick.title": "Create content for locale",
"commands.i18n.create.quickPick.placeHolder": "To which locale do you want to create a new content?",
"commands.i18n.translate.progress.title": "Translating content...",
"commands.preview.panel.title": "Preview: {0}",
"commands.preview.askUserToPickFolder.title": "Select the folder of the article to preview",
+188 -146
View File
@@ -10,7 +10,8 @@
"color": "#0e131f",
"theme": "dark"
},
"badges": [{
"badges": [
{
"description": "version",
"url": "https://img.shields.io/github/package-json/v/estruyf/vscode-front-matter?color=green&label=vscode-front-matter&style=flat-square",
"href": "https://github.com/estruyf/vscode-front-matter"
@@ -70,7 +71,8 @@
"**/.frontmatter/config/*.json": "jsonc"
}
},
"keybindings": [{
"keybindings": [
{
"command": "frontMatter.dashboard",
"key": "alt+d"
},
@@ -88,19 +90,23 @@
}
],
"viewsContainers": {
"activitybar": [{
"id": "frontmatter-explorer",
"title": "FM",
"icon": "$(fm-logo)"
}]
"activitybar": [
{
"id": "frontmatter-explorer",
"title": "FM",
"icon": "$(fm-logo)"
}
]
},
"views": {
"frontmatter-explorer": [{
"id": "frontMatter.explorer",
"name": "Front Matter",
"icon": "$(fm-logo)",
"type": "webview"
}]
"frontmatter-explorer": [
{
"id": "frontMatter.explorer",
"name": "Front Matter",
"icon": "$(fm-logo)",
"type": "webview"
}
]
},
"configuration": {
"title": "%settings.configuration.title%",
@@ -168,7 +174,8 @@
"frontMatter.content.defaultFileType": {
"type": "string",
"default": "md",
"oneOf": [{
"oneOf": [
{
"enum": [
"md",
"mdx"
@@ -184,7 +191,8 @@
"frontMatter.content.defaultSorting": {
"type": "string",
"default": "",
"oneOf": [{
"oneOf": [
{
"enum": [
"LastModifiedAsc",
"LastModifiedDesc",
@@ -531,7 +539,8 @@
"categories"
],
"markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%",
"items": [{
"items": [
{
"type": "string"
},
{
@@ -599,7 +608,8 @@
"command": {
"$id": "#scriptCommand",
"type": "string",
"anyOf": [{
"anyOf": [
{
"enum": [
"node",
"bash",
@@ -806,7 +816,8 @@
"title",
"file"
],
"anyOf": [{
"anyOf": [
{
"required": [
"schema"
]
@@ -860,7 +871,8 @@
"id",
"path"
],
"anyOf": [{
"anyOf": [
{
"required": [
"schema"
]
@@ -1101,26 +1113,29 @@
}
}
},
"default": [{
"name": "default",
"fileTypes": null,
"fields": [{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Caption",
"name": "caption",
"type": "string"
},
{
"title": "Alt text",
"name": "alt",
"type": "string"
}
]
}],
"default": [
{
"name": "default",
"fileTypes": null,
"fields": [
{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Caption",
"name": "caption",
"type": "string"
},
{
"title": "Alt text",
"name": "alt",
"type": "string"
}
]
}
],
"scope": "Media"
},
"frontMatter.media.supportedMimeTypes": {
@@ -1350,7 +1365,8 @@
"default": "",
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%",
"not": {
"anyOf": [{
"anyOf": [
{
"const": ""
},
{
@@ -1544,7 +1560,8 @@
"type",
"name"
],
"allOf": [{
"allOf": [
{
"if": {
"properties": {
"type": {
@@ -1752,48 +1769,51 @@
"fields"
]
},
"default": [{
"name": "default",
"pageBundle": false,
"fields": [{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Description",
"name": "description",
"type": "string"
},
{
"title": "Publishing date",
"name": "date",
"type": "datetime",
"default": "{{now}}",
"isPublishDate": true
},
{
"title": "Content preview",
"name": "preview",
"type": "image"
},
{
"title": "Is in draft",
"name": "draft",
"type": "boolean"
},
{
"title": "Tags",
"name": "tags",
"type": "tags"
},
{
"title": "Categories",
"name": "categories",
"type": "categories"
}
]
}],
"default": [
{
"name": "default",
"pageBundle": false,
"fields": [
{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Description",
"name": "description",
"type": "string"
},
{
"title": "Publishing date",
"name": "date",
"type": "datetime",
"default": "{{now}}",
"isPublishDate": true
},
{
"title": "Content preview",
"name": "preview",
"type": "image"
},
{
"title": "Is in draft",
"name": "draft",
"type": "boolean"
},
{
"title": "Tags",
"name": "tags",
"type": "tags"
},
{
"title": "Categories",
"name": "categories",
"type": "categories"
}
]
}
],
"scope": "Taxonomy"
},
"frontMatter.taxonomy.customTaxonomy": {
@@ -1806,7 +1826,8 @@
"type": "string",
"description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%",
"not": {
"anyOf": [{
"anyOf": [
{
"const": ""
},
{
@@ -2000,10 +2021,16 @@
"frontMatter.website.host": {
"type": "string",
"markdownDescription": "%setting.frontMatter.website.host.markdownDescription%"
},
"frontMatter.integration.deepl": {
"type": "string",
"default": "",
"markdownDescription": "%setting.frontMatter.integration.deepl.markdownDescription%"
}
}
},
"commands": [{
"commands": [
{
"command": "frontMatter.project.switch",
"title": "%command.frontMatter.project.switch%",
"category": "Front Matter",
@@ -2329,16 +2356,21 @@
}
}
],
"submenus": [{
"id": "frontmatter.submenu",
"label": "Front Matter"
}],
"submenus": [
{
"id": "frontmatter.submenu",
"label": "Front Matter"
}
],
"menus": {
"webview/context": [{
"command": "workbench.action.webview.openDeveloperTools",
"when": "frontMatter:isDevelopment"
}],
"editor/title": [{
"webview/context": [
{
"command": "workbench.action.webview.openDeveloperTools",
"when": "frontMatter:isDevelopment"
}
],
"editor/title": [
{
"command": "frontMatter.markup.heading",
"group": "navigation@-133",
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
@@ -2424,11 +2456,14 @@
"when": "resourceFilename == 'frontmatter.json'"
}
],
"explorer/context": [{
"submenu": "frontmatter.submenu",
"group": "frontmatter@1"
}],
"frontmatter.submenu": [{
"explorer/context": [
{
"submenu": "frontmatter.submenu",
"group": "frontmatter@1"
}
],
"frontmatter.submenu": [
{
"command": "frontMatter.createFromTemplate",
"when": "explorerResourceIsFolder",
"group": "frontmatter@1"
@@ -2444,7 +2479,8 @@
"group": "frontmatter@3"
}
],
"commandPalette": [{
"commandPalette": [
{
"command": "frontMatter.init",
"when": "frontMatterCanInit"
},
@@ -2597,7 +2633,8 @@
"when": "frontMatter:file:isValid == true"
}
],
"view/title": [{
"view/title": [
{
"command": "frontMatter.chatbot",
"group": "navigation@0",
"when": "view == frontMatter.explorer"
@@ -2629,52 +2666,57 @@
}
]
},
"grammars": [{
"path": "./syntaxes/hugo.tmLanguage.json",
"scopeName": "frontmatter.markdown.hugo",
"injectTo": [
"text.html.markdown"
]
}],
"walkthroughs": [{
"id": "frontmatter.welcome",
"title": "Get started with Front Matter",
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
"steps": [{
"id": "frontmatter.welcome.init",
"title": "Get started",
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
"media": {
"markdown": "assets/walkthrough/get-started.md"
"grammars": [
{
"path": "./syntaxes/hugo.tmLanguage.json",
"scopeName": "frontmatter.markdown.hugo",
"injectTo": [
"text.html.markdown"
]
}
],
"walkthroughs": [
{
"id": "frontmatter.welcome",
"title": "Get started with Front Matter",
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
"steps": [
{
"id": "frontmatter.welcome.init",
"title": "Get started",
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
"media": {
"markdown": "assets/walkthrough/get-started.md"
},
"completionEvents": [
"onContext:frontMatterInitialized"
]
},
"completionEvents": [
"onContext:frontMatterInitialized"
]
},
{
"id": "frontmatter.welcome.documentation",
"title": "Documentation",
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
"media": {
"markdown": "assets/walkthrough/documentation.md"
{
"id": "frontmatter.welcome.documentation",
"title": "Documentation",
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
"media": {
"markdown": "assets/walkthrough/documentation.md"
},
"completionEvents": [
"onLink:https://frontmatter.codes/docs"
]
},
"completionEvents": [
"onLink:https://frontmatter.codes/docs"
]
},
{
"id": "frontmatter.welcome.supporter",
"title": "Support the project",
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
"media": {
"markdown": "assets/walkthrough/support-the-project.md"
},
"completionEvents": [
"onLink:https://github.com/sponsors/estruyf"
]
}
]
}]
{
"id": "frontmatter.welcome.supporter",
"title": "Support the project",
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
"media": {
"markdown": "assets/walkthrough/support-the-project.md"
},
"completionEvents": [
"onLink:https://github.com/sponsors/estruyf"
]
}
]
}
]
},
"scripts": {
"dev:ext": "npm run clean && npm run localization:generate && npm-run-all --parallel watch:*",
@@ -2803,4 +2845,4 @@
"dependencies": {
"@radix-ui/react-dropdown-menu": "^2.0.6"
}
}
}
+1
View File
@@ -268,6 +268,7 @@
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.customType.description": "Specify the name of the custom field type to use.",
"setting.frontMatter.taxonomy.contentTypes.items.properties.clearEmpty.description": "Specify if the empty values should be cleared.",
"setting.frontMatter.website.host.markdownDescription": "Specify the host URL of your website. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.website.url)",
"setting.frontMatter.integration.deepl.markdownDescription": "Specify the DeepL API key to use for the translation of your content. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.integration.deepl)",
"command.frontMatter.settings.refresh": "Refresh Front Matter Settings",
"setting.frontMatter.config.dynamicFilePath.markdownDescription": "Specify the path to the dynamic config file (ex: [[workspace]]/config.js). [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.config.dynamicfilepath)",
"setting.frontMatter.taxonomy.contentTypes.items.properties.allowAsSubContent.description": "Specify if the content type can be used as sub content.",
+86 -9
View File
@@ -1,15 +1,16 @@
import { Uri, commands, window, workspace } from 'vscode';
import { ProgressLocation, Uri, commands, window, workspace } from 'vscode';
import {
ArticleHelper,
ContentType,
Extension,
FrameworkDetector,
Logger,
Notifications,
Settings,
openFileInEditor,
parseWinPath
} from '../helpers';
import { COMMAND_NAME, SETTING_CONTENT_I18N } from '../constants';
import { COMMAND_NAME, SETTING_CONTENT_I18N, SETTING_INTEGRATION_DEEPL } from '../constants';
import { ContentFolder, Field, I18nConfig, ContentType as IContentType } from '../models';
import { join, parse } from 'path';
import { existsAsync } from '../utils';
@@ -19,12 +20,10 @@ import { PagesListener } from '../listeners/dashboard';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
// TODO:
// Allow sponsors to automatically translate the content
// Support for DeepL, Azure
export class i18n {
private static processedFiles: { [filePath: string]: { dir: string; filename: string; isPageBundle: boolean; }} = {};
private static processedFiles: {
[filePath: string]: { dir: string; filename: string; isPageBundle: boolean };
} = {};
/**
* Registers the i18n commands.
@@ -33,7 +32,7 @@ export class i18n {
const subscriptions = Extension.getInstance().subscriptions;
subscriptions.push(commands.registerCommand(COMMAND_NAME.i18n.create, i18n.create));
i18n.clearFiles();
}
@@ -301,6 +300,11 @@ export class i18n {
return;
}
const sourceLocale = await i18n.getLocale(fileUri.fsPath);
if (sourceLocale?.locale) {
article = await i18n.translate(article, sourceLocale, selectedI18n);
}
const newFileUri = Uri.file(newFilePath);
await workspace.fs.writeFile(
newFileUri,
@@ -319,6 +323,79 @@ export class i18n {
);
}
/**
* Translates the given article from the source locale to the target locale using DeepL translation service.
* @param article - The article to be translated.
* @param sourceLocale - The source locale configuration.
* @param targetLocale - The target locale configuration.
* @returns A promise that resolves to the translated article.
*/
private static async translate(
article: ParsedFrontMatter,
sourceLocale: I18nConfig,
targetLocale: I18nConfig
) {
return new Promise<ParsedFrontMatter>(async (resolve) => {
const authKey = Settings.get<string>(SETTING_INTEGRATION_DEEPL);
if (!authKey) {
resolve(article);
return;
}
await window.withProgress(
{
location: ProgressLocation.Notification,
title: l10n.t(LocalizationKey.commandsI18nTranslateProgressTitle),
cancellable: false
},
async () => {
const title = article.data.title;
const description = article.data.description;
const content = article.content;
try {
const body = JSON.stringify({
text: [title, description, content],
source_lang: sourceLocale.locale,
target_lang: targetLocale.locale
});
let host = authKey.endsWith(':fx') ? 'api-free.deepl.com' : 'api.deepl.com';
const response = await fetch(`https://${host}/v2/translate`, {
method: 'POST',
headers: {
Authorization: `DeepL-Auth-Key ${authKey}`,
'User-Agent': `FrontMatterCMS/${Extension.getInstance().version}`,
'Content-Type': 'application/json',
'content-length': body.length.toString(),
Accept: 'application/json'
},
body
});
if (!response.ok) {
throw new Error(`DeepL: ${response.statusText}`);
}
const data = await response.json();
if (!data.translations || data.translations.length < 3) {
throw new Error('DeepL: Invalid response');
}
article.data.title = data.translations[0].text;
article.data.description = data.translations[1].text;
article.content = data.translations[2].text;
} catch (error) {
Notifications.error(`${(error as Error).message}`);
}
resolve(article);
}
);
});
}
/**
* Retrieves the filename and directory information from the given file path.
* If the file is a page bundle, the directory will be adjusted accordingly.
@@ -348,7 +425,7 @@ export class i18n {
isPageBundle,
filename,
dir
}
};
return i18n.processedFiles[filePath];
}
+1 -1
View File
@@ -3,7 +3,7 @@
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { cn } from "../../utils/cn"
import { CheckCircleIcon, CheckIcon, ChevronRightIcon } from "@heroicons/react/24/outline"
import { ChevronRightIcon } from "@heroicons/react/24/outline"
const DropdownMenu = DropdownMenuPrimitive.Root
+2
View File
@@ -114,6 +114,8 @@ export const SETTING_SNIPPETS_WRAPPER = 'snippets.wrapper.enabled';
export const SETTING_WEBSITE_URL = 'website.host';
export const SETTING_INTEGRATION_DEEPL = 'integration.deepl';
/**
* Sponsors only settings
*/
+4
View File
@@ -1724,6 +1724,10 @@ export enum LocalizationKey {
* To which locale do you want to create a new content?
*/
commandsI18nCreateQuickPickPlaceHolder = 'commands.i18n.create.quickPick.placeHolder',
/**
* Translating content...
*/
commandsI18nTranslateProgressTitle = 'commands.i18n.translate.progress.title',
/**
* Preview: {0}
*/