mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-07 01:13:08 +02:00
Merge branch 'azure-translator' into dev
This commit is contained in:
@@ -67,8 +67,14 @@
|
||||
"settings.commonSettings.startCommand": "SSG/Framework start command",
|
||||
|
||||
"settings.integrationsView.deepl.title": "DeepL",
|
||||
"settings.integrationsView.deepl.intput.label": "Authentication key",
|
||||
"settings.integrationsView.deepl.intput.placeholder": "Enter your DeepL authentication key",
|
||||
"settings.integrationsView.deepl.intput.label": "API key",
|
||||
"settings.integrationsView.deepl.intput.placeholder": "Enter your Azure Translator API key",
|
||||
|
||||
"settings.integrationsView.azure.title": "Azure AI Translator Service",
|
||||
"settings.integrationsView.azure.intput.label": "Subscription key",
|
||||
"settings.integrationsView.azure.intput.placeholder": "Enter your Azure AI Translator - Subscription key",
|
||||
"settings.integrationsView.azure.region.label": "Region",
|
||||
"settings.integrationsView.azure.region.placeholder": "Enter your Azure AI Translator - Region. Example: westeurope",
|
||||
|
||||
"developer.title": "Developer mode",
|
||||
"developer.reload.title": "Reload the dashboard",
|
||||
|
||||
+16
-39
@@ -4,13 +4,12 @@ import {
|
||||
ContentType,
|
||||
Extension,
|
||||
FrameworkDetector,
|
||||
Logger,
|
||||
Notifications,
|
||||
Settings,
|
||||
openFileInEditor,
|
||||
parseWinPath
|
||||
} from '../helpers';
|
||||
import { COMMAND_NAME, ExtensionState, SETTING_CONTENT_I18N } from '../constants';
|
||||
import { COMMAND_NAME, SETTING_CONTENT_I18N } from '../constants';
|
||||
import { ContentFolder, Field, I18nConfig, ContentType as IContentType } from '../models';
|
||||
import { join, parse } from 'path';
|
||||
import { existsAsync } from '../utils';
|
||||
@@ -19,6 +18,7 @@ import { ParsedFrontMatter } from '../parsers';
|
||||
import { PagesListener } from '../listeners/dashboard';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { Translations } from '../services/Translations';
|
||||
|
||||
export class i18n {
|
||||
private static processedFiles: {
|
||||
@@ -380,12 +380,6 @@ export class i18n {
|
||||
targetLocale: I18nConfig
|
||||
) {
|
||||
return new Promise<ParsedFrontMatter>(async (resolve) => {
|
||||
const authKey = await Extension.getInstance().getSecret(ExtensionState.Secrets.DeeplApiKey);
|
||||
if (!authKey) {
|
||||
resolve(article);
|
||||
return;
|
||||
}
|
||||
|
||||
await window.withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
@@ -393,42 +387,25 @@ export class i18n {
|
||||
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
|
||||
});
|
||||
const title = article.data.title || '';
|
||||
const description = article.data.description || '';
|
||||
const content = article.content || '';
|
||||
|
||||
let host = authKey.endsWith(':fx') ? 'api-free.deepl.com' : 'api.deepl.com';
|
||||
const text = [title, description, content];
|
||||
const translations = await Translations.translate(
|
||||
text,
|
||||
sourceLocale.locale,
|
||||
targetLocale.locale
|
||||
);
|
||||
|
||||
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',
|
||||
Accept: 'application/json'
|
||||
},
|
||||
body
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`DeepL: ${response.statusText}`);
|
||||
if (!translations || translations.length < 3) {
|
||||
throw new Error('Invalid response');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!data.translations || data.translations.length < 3) {
|
||||
throw new Error('DeepL: Invalid response');
|
||||
}
|
||||
|
||||
article.data.title = article.data.title ? data.translations[0].text : '';
|
||||
article.data.description = article.data.description ? data.translations[1].text : '';
|
||||
article.content = article.content ? data.translations[2].text : '';
|
||||
article.data.title = article.data.title ? translations[0] : '';
|
||||
article.data.description = article.data.description ? translations[1] : '';
|
||||
article.content = article.content ? translations[2] : '';
|
||||
} catch (error) {
|
||||
Notifications.error(`${(error as Error).message}`);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,12 @@ export const ExtensionState = {
|
||||
},
|
||||
|
||||
Secrets: {
|
||||
DeeplApiKey: `frontMatter:Secrets:DeeplApiKey`
|
||||
Deepl: {
|
||||
ApiKey: `frontMatter:Secrets:DeeplApiKey`
|
||||
},
|
||||
Azure: {
|
||||
TranslatorKey: `frontMatter:Secrets:AzureTranslatorKey`,
|
||||
TranslatorRegion: `frontMatter:Secrets:AzureTranslatorRegion`
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,26 +10,64 @@ export interface IIntegrationsViewProps { }
|
||||
|
||||
export const IntegrationsView: React.FunctionComponent<IIntegrationsViewProps> = ({ }: React.PropsWithChildren<IIntegrationsViewProps>) => {
|
||||
const [deeplApiKey, setDeeplApiKey] = React.useState<string>('');
|
||||
const [azureApiKey, setAzureApiKey] = React.useState<string>('');
|
||||
const [azureRegion, setAzureRegion] = React.useState<string>('');
|
||||
const [crntDeeplApiKey, setCrntDeeplApiKey] = React.useState<string>('');
|
||||
const [crntAzureApiKey, setCrntAzureApiKey] = React.useState<string>('');
|
||||
const [crntAzureRegion, setCrntAzureRegion] = React.useState<string>('');
|
||||
|
||||
const onSave = React.useCallback(() => {
|
||||
messageHandler.request<string>(GeneralCommands.toVSCode.secrets.set, {
|
||||
key: ExtensionState.Secrets.DeeplApiKey,
|
||||
value: crntDeeplApiKey
|
||||
}).then((apiKey: string) => {
|
||||
setDeeplApiKey(apiKey);
|
||||
});
|
||||
}, [crntDeeplApiKey]);
|
||||
if (crntDeeplApiKey !== deeplApiKey) {
|
||||
messageHandler.request<string>(GeneralCommands.toVSCode.secrets.set, {
|
||||
key: ExtensionState.Secrets.Deepl.ApiKey,
|
||||
value: crntDeeplApiKey
|
||||
}).then((apiKey: string) => {
|
||||
setDeeplApiKey(apiKey);
|
||||
});
|
||||
}
|
||||
|
||||
const onChange = (_: string, value: string) => {
|
||||
setCrntDeeplApiKey(value);
|
||||
if (crntAzureApiKey !== azureApiKey) {
|
||||
messageHandler.request<string>(GeneralCommands.toVSCode.secrets.set, {
|
||||
key: ExtensionState.Secrets.Azure.TranslatorKey,
|
||||
value: crntAzureApiKey
|
||||
}).then((apiKey: string) => {
|
||||
setAzureApiKey(apiKey);
|
||||
});
|
||||
}
|
||||
|
||||
if (crntAzureRegion !== azureRegion) {
|
||||
messageHandler.request<string>(GeneralCommands.toVSCode.secrets.set, {
|
||||
key: ExtensionState.Secrets.Azure.TranslatorRegion,
|
||||
value: crntAzureRegion
|
||||
}).then((apiKey: string) => {
|
||||
setAzureRegion(apiKey);
|
||||
});
|
||||
}
|
||||
}, [crntDeeplApiKey, deeplApiKey, crntAzureApiKey, azureApiKey, crntAzureRegion, azureRegion]);
|
||||
|
||||
const onChange = (key: string, value: string) => {
|
||||
if (key === ExtensionState.Secrets.Deepl.ApiKey) {
|
||||
setCrntDeeplApiKey(value);
|
||||
} else if (key === ExtensionState.Secrets.Azure.TranslatorKey) {
|
||||
setCrntAzureApiKey(value);
|
||||
} else if (key === ExtensionState.Secrets.Azure.TranslatorRegion) {
|
||||
setCrntAzureRegion(value);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
messageHandler.request<string>(GeneralCommands.toVSCode.secrets.get, ExtensionState.Secrets.DeeplApiKey).then((apiKey: string) => {
|
||||
messageHandler.request<string>(GeneralCommands.toVSCode.secrets.get, ExtensionState.Secrets.Deepl.ApiKey).then((apiKey: string) => {
|
||||
setDeeplApiKey(apiKey);
|
||||
setCrntDeeplApiKey(apiKey);
|
||||
});
|
||||
messageHandler.request<string>(GeneralCommands.toVSCode.secrets.get, ExtensionState.Secrets.Azure.TranslatorKey).then((apiKey: string) => {
|
||||
setAzureApiKey(apiKey);
|
||||
setCrntAzureApiKey(apiKey);
|
||||
});
|
||||
messageHandler.request<string>(GeneralCommands.toVSCode.secrets.get, ExtensionState.Secrets.Azure.TranslatorRegion).then((apiKey: string) => {
|
||||
setAzureRegion(apiKey);
|
||||
setCrntAzureRegion(apiKey);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -39,16 +77,37 @@ export const IntegrationsView: React.FunctionComponent<IIntegrationsViewProps> =
|
||||
|
||||
<SettingsInput
|
||||
label={l10n.t(LocalizationKey.settingsIntegrationsViewDeeplIntputLabel)}
|
||||
name={ExtensionState.Secrets.DeeplApiKey}
|
||||
name={ExtensionState.Secrets.Deepl.ApiKey}
|
||||
value={crntDeeplApiKey || ""}
|
||||
placeholder={l10n.t(LocalizationKey.settingsIntegrationsViewDeeplIntputPlaceholder)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsIntegrationsViewAzureTitle)}</h2>
|
||||
|
||||
<SettingsInput
|
||||
label={l10n.t(LocalizationKey.settingsIntegrationsViewAzureIntputLabel)}
|
||||
name={ExtensionState.Secrets.Azure.TranslatorKey}
|
||||
value={crntAzureApiKey || ""}
|
||||
placeholder={l10n.t(LocalizationKey.settingsIntegrationsViewAzureIntputPlaceholder)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<SettingsInput
|
||||
label={l10n.t(LocalizationKey.settingsIntegrationsViewAzureRegionLabel)}
|
||||
name={ExtensionState.Secrets.Azure.TranslatorRegion}
|
||||
value={crntAzureRegion || ""}
|
||||
placeholder={l10n.t(LocalizationKey.settingsIntegrationsViewAzureRegionPlaceholder)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
<div className={`mt-4 flex gap-2`}>
|
||||
<VSCodeButton
|
||||
onClick={onSave}
|
||||
disabled={deeplApiKey === crntDeeplApiKey}>
|
||||
disabled={
|
||||
deeplApiKey === crntDeeplApiKey &&
|
||||
azureApiKey === crntAzureApiKey &&
|
||||
azureRegion === crntAzureRegion
|
||||
}>
|
||||
{l10n.t(LocalizationKey.commonSave)}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
@@ -248,13 +248,33 @@ export enum LocalizationKey {
|
||||
*/
|
||||
settingsIntegrationsViewDeeplTitle = 'settings.integrationsView.deepl.title',
|
||||
/**
|
||||
* Authentication key
|
||||
* API key
|
||||
*/
|
||||
settingsIntegrationsViewDeeplIntputLabel = 'settings.integrationsView.deepl.intput.label',
|
||||
/**
|
||||
* Enter your DeepL authentication key
|
||||
* Enter your Azure Translator API key
|
||||
*/
|
||||
settingsIntegrationsViewDeeplIntputPlaceholder = 'settings.integrationsView.deepl.intput.placeholder',
|
||||
/**
|
||||
* Azure AI Translator Service
|
||||
*/
|
||||
settingsIntegrationsViewAzureTitle = 'settings.integrationsView.azure.title',
|
||||
/**
|
||||
* Subscription key
|
||||
*/
|
||||
settingsIntegrationsViewAzureIntputLabel = 'settings.integrationsView.azure.intput.label',
|
||||
/**
|
||||
* Enter your Azure AI Translator - Subscription key
|
||||
*/
|
||||
settingsIntegrationsViewAzureIntputPlaceholder = 'settings.integrationsView.azure.intput.placeholder',
|
||||
/**
|
||||
* Region
|
||||
*/
|
||||
settingsIntegrationsViewAzureRegionLabel = 'settings.integrationsView.azure.region.label',
|
||||
/**
|
||||
* Enter your Azure AI Translator - Region. Example: westeurope
|
||||
*/
|
||||
settingsIntegrationsViewAzureRegionPlaceholder = 'settings.integrationsView.azure.region.placeholder',
|
||||
/**
|
||||
* Developer mode
|
||||
*/
|
||||
@@ -2156,18 +2176,6 @@ export enum LocalizationKey {
|
||||
* {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
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { ExtensionState } from '../constants';
|
||||
import { Extension } from '../helpers';
|
||||
|
||||
export class Translations {
|
||||
/**
|
||||
* Translates an array of text from a source language to a target language.
|
||||
* @param text - The array of text to be translated.
|
||||
* @param source - The source language code.
|
||||
* @param target - The target language code.
|
||||
* @returns A Promise that resolves to an array of translated text, or undefined if translation is not possible.
|
||||
*/
|
||||
public static async translate(
|
||||
text: string[],
|
||||
source: string,
|
||||
target: string
|
||||
): Promise<string[] | undefined> {
|
||||
const deeplAuthKey = await Extension.getInstance().getSecret(
|
||||
ExtensionState.Secrets.Deepl.ApiKey
|
||||
);
|
||||
const azureAuthKey = await Extension.getInstance().getSecret(
|
||||
ExtensionState.Secrets.Azure.TranslatorKey
|
||||
);
|
||||
const azureRegion = await Extension.getInstance().getSecret(
|
||||
ExtensionState.Secrets.Azure.TranslatorRegion
|
||||
);
|
||||
|
||||
if (azureAuthKey && azureRegion) {
|
||||
return this.translateAzure(text, source, target, azureAuthKey, azureRegion);
|
||||
}
|
||||
|
||||
if (deeplAuthKey) {
|
||||
return this.translateDeepL(text, source, target, deeplAuthKey);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates an array of text using Azure Cognitive Services Translator API.
|
||||
* @param text - The array of text to be translated.
|
||||
* @param source - The source language code.
|
||||
* @param target - The target language code.
|
||||
* @param azureAuthKey - The Azure authentication key.
|
||||
* @param azureRegion - The Azure region for the translation service.
|
||||
* @returns A promise that resolves to an array of translated text.
|
||||
* @throws An error if the translation fails.
|
||||
*/
|
||||
private static async translateAzure(
|
||||
text: string[],
|
||||
source: string,
|
||||
target: string,
|
||||
azureAuthKey: string,
|
||||
azureRegion: string
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
const body = JSON.stringify(text.map((t) => ({ Text: t })));
|
||||
|
||||
const response = await fetch(
|
||||
`https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=${target}&from=${source}&textType=html`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Ocp-Apim-Subscription-Key': azureAuthKey,
|
||||
'Ocp-Apim-Subscription-Region': azureRegion,
|
||||
'Content-Type': 'application/json; charset=UTF-8'
|
||||
},
|
||||
body
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return data.map((t: { translations: { text: string }[] }) => t.translations[0].text);
|
||||
} catch (error) {
|
||||
throw new Error(`Azure: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates an array of text using the DeepL translation service.
|
||||
* @param text - The text to be translated.
|
||||
* @param source - The source language of the text.
|
||||
* @param target - The target language for the translation.
|
||||
* @param deeplAuthKey - The authentication key for accessing the DeepL API.
|
||||
* @returns A Promise that resolves to an array of translated text.
|
||||
* @throws If there is an error during the translation process.
|
||||
*/
|
||||
private static async translateDeepL(
|
||||
text: string[],
|
||||
source: string,
|
||||
target: string,
|
||||
deeplAuthKey: string
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
const body = JSON.stringify({
|
||||
text,
|
||||
source_lang: source,
|
||||
target_lang: target
|
||||
});
|
||||
|
||||
let host = deeplAuthKey.endsWith(':fx') ? 'api-free.deepl.com' : 'api.deepl.com';
|
||||
|
||||
const response = await fetch(`https://${host}/v2/translate`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `DeepL-Auth-Key ${deeplAuthKey}`,
|
||||
'User-Agent': `FrontMatterCMS/${Extension.getInstance().version}`,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json'
|
||||
},
|
||||
body
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!data.translations || data.translations.length < 3) {
|
||||
throw new Error('Invalid response');
|
||||
}
|
||||
|
||||
return data.translations.map((t: { text: string }) => t.text);
|
||||
} catch (error) {
|
||||
throw new Error(`DeepL: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user