enhance(frontend): 実験的機能としてTranslator APIを用いた翻訳を実装 (#16600)
* enhance(frontend): 実験的機能としてTranslator APIを用いた翻訳を実装 * remove unused imports * remove unnecessary console.log * fix 表記揺れ * fix lint
This commit is contained in:
parent
7796fce779
commit
46b0e8115a
|
@ -102,6 +102,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSwitch v-model="enableHapticFeedback">
|
<MkSwitch v-model="enableHapticFeedback">
|
||||||
<template #label>Enable haptic feedback</template>
|
<template #label>Enable haptic feedback</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="enableWebTranslatorApi">
|
||||||
|
<template #label>Enable in-browser translator API</template>
|
||||||
|
</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</SearchMarker>
|
</SearchMarker>
|
||||||
|
@ -182,6 +185,7 @@ const devMode = prefer.model('devMode');
|
||||||
const stackingRouterView = prefer.model('experimental.stackingRouterView');
|
const stackingRouterView = prefer.model('experimental.stackingRouterView');
|
||||||
const enableFolderPageView = prefer.model('experimental.enableFolderPageView');
|
const enableFolderPageView = prefer.model('experimental.enableFolderPageView');
|
||||||
const enableHapticFeedback = prefer.model('experimental.enableHapticFeedback');
|
const enableHapticFeedback = prefer.model('experimental.enableHapticFeedback');
|
||||||
|
const enableWebTranslatorApi = prefer.model('experimental.enableWebTranslatorApi');
|
||||||
|
|
||||||
watch(skipNoteRender, () => {
|
watch(skipNoteRender, () => {
|
||||||
suggestReload();
|
suggestReload();
|
||||||
|
|
|
@ -516,4 +516,7 @@ export const PREF_DEF = definePreferences({
|
||||||
'experimental.enableHapticFeedback': {
|
'experimental.enableHapticFeedback': {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
'experimental.enableWebTranslatorApi': {
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { defineAsyncComponent } from 'vue';
|
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { url } from '@@/js/config.js';
|
import { url } from '@@/js/config.js';
|
||||||
import { claimAchievement } from './achievements.js';
|
import { claimAchievement } from './achievements.js';
|
||||||
|
@ -27,6 +26,11 @@ import { prefer } from '@/preferences.js';
|
||||||
import { getPluginHandlers } from '@/plugin.js';
|
import { getPluginHandlers } from '@/plugin.js';
|
||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
|
|
||||||
|
const isInBrowserTranslationAvailable = (
|
||||||
|
'LanguageDetector' in window &&
|
||||||
|
'Translator' in window
|
||||||
|
);
|
||||||
|
|
||||||
export async function getNoteClipMenu(props: {
|
export async function getNoteClipMenu(props: {
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
currentClip?: Misskey.entities.Clip;
|
currentClip?: Misskey.entities.Clip;
|
||||||
|
@ -285,13 +289,48 @@ export function getNoteMenu(props: {
|
||||||
|
|
||||||
async function translate(): Promise<void> {
|
async function translate(): Promise<void> {
|
||||||
if (props.translation.value != null) return;
|
if (props.translation.value != null) return;
|
||||||
props.translating.value = true;
|
if (prefer.s['experimental.enableWebTranslatorApi'] && isInBrowserTranslationAvailable && appearNote.text != null) {
|
||||||
const res = await misskeyApi('notes/translate', {
|
props.translating.value = true;
|
||||||
noteId: appearNote.id,
|
try {
|
||||||
targetLang: miLocalStorage.getItem('lang') ?? navigator.language,
|
// @ts-expect-error 実験的なAPIなので型定義がない
|
||||||
});
|
const detector = await LanguageDetector.create();
|
||||||
props.translating.value = false;
|
const langResult = await detector.detect(appearNote.text);
|
||||||
props.translation.value = res;
|
let localStorageLang = miLocalStorage.getItem('lang');
|
||||||
|
if (localStorageLang != null) {
|
||||||
|
localStorageLang = localStorageLang.split('-')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 翻訳元と翻訳先の言語が同じ場合はTranslatorがthrowするのでそのまま返す
|
||||||
|
if (langResult[0]?.detectedLanguage === localStorageLang || langResult[0]?.detectedLanguage === navigator.language) {
|
||||||
|
props.translation.value = {
|
||||||
|
sourceLang: langResult[0]?.detectedLanguage ?? 'unknown',
|
||||||
|
text: appearNote.text,
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error 実験的なAPIなので型定義がない
|
||||||
|
const translator = await Translator.create({
|
||||||
|
sourceLanguage: langResult[0]?.detectedLanguage,
|
||||||
|
targetLanguage: localStorageLang ?? navigator.language,
|
||||||
|
});
|
||||||
|
const translated = await translator.translate(appearNote.text);
|
||||||
|
props.translation.value = {
|
||||||
|
sourceLang: langResult[0]?.detectedLanguage ?? 'unknown',
|
||||||
|
text: translated,
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
props.translating.value = false;
|
||||||
|
}
|
||||||
|
} else if ($i?.policies.canUseTranslator && instance.translatorAvailable) {
|
||||||
|
props.translating.value = true;
|
||||||
|
const res = await misskeyApi('notes/translate', {
|
||||||
|
noteId: appearNote.id,
|
||||||
|
targetLang: miLocalStorage.getItem('lang') ?? navigator.language,
|
||||||
|
});
|
||||||
|
props.translating.value = false;
|
||||||
|
props.translation.value = res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuItems: MenuItem[] = [];
|
const menuItems: MenuItem[] = [];
|
||||||
|
@ -349,7 +388,7 @@ export function getNoteMenu(props: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($i.policies.canUseTranslator && instance.translatorAvailable) {
|
if ((prefer.s['experimental.enableWebTranslatorApi'] && isInBrowserTranslationAvailable) || ($i.policies.canUseTranslator && instance.translatorAvailable)) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
icon: 'ti ti-language-hiragana',
|
icon: 'ti ti-language-hiragana',
|
||||||
text: i18n.ts.translate,
|
text: i18n.ts.translate,
|
||||||
|
|
Loading…
Reference in New Issue