From e484545d5effff8b62da6967b2bf549391d5d32e Mon Sep 17 00:00:00 2001 From: fly_mc <me@flymc.cc> Date: Thu, 17 Oct 2024 23:19:17 +0800 Subject: [PATCH] add translate foreign language in MkNote --- packages/frontend-embed/vite.config.ts | 7 ++++++ packages/frontend/package.json | 1 + packages/frontend/src/components/MkNote.vue | 23 +++++++++++++++++++ .../frontend/src/scripts/detect-language.ts | 18 +++++++++++++++ packages/frontend/vite.config.ts | 7 ++++++ 5 files changed, 56 insertions(+) create mode 100644 packages/frontend/src/scripts/detect-language.ts diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts index 2dbee488c5..fe134a000d 100644 --- a/packages/frontend-embed/vite.config.ts +++ b/packages/frontend-embed/vite.config.ts @@ -25,6 +25,13 @@ const externalPackages = [ : id; }, }, + { + name: 'tinyld', + match: /^tinyld$/, + path(): string { + return `https://cdn.jsdelivr.net/npm/tinyld@${packageInfo.dependencies.tinyld}/dist/tinyld.normal.node.mjs` + }, + }, ]; const hash = (str: string, seed = 0): number => { diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 0d9ffc0805..6f1df2fa84 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -67,6 +67,7 @@ "three": "0.169.0", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", + "tinyld": "^1.3.4", "tsc-alias": "1.8.10", "tsconfig-paths": "4.2.0", "typescript": "5.6.2", diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 202e9d8e2d..1ba10bd684 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -80,6 +80,10 @@ SPDX-License-Identifier: AGPL-3.0-only :enableEmojiMenu="true" :enableEmojiMenuReaction="true" /> + <div v-if="instance.translatorAvailable && $i && $i.policies.canUseTranslator && appearNote.text && isForeignLanguage" style="padding-top: 5px; color: var(--MI_THEME-accent);"> + <button v-if="!(translating || translation)" ref="translateButton" class="_button" @click.stop="translate()">{{ i18n.ts.translate }}</button> + <button v-else class="_button" @click.stop="translation= null">{{ i18n.ts.close }}</button> + </div> <div v-if="translating || translation" :class="$style.translation"> <MkLoading v-if="translating" mini/> <div v-else-if="translation"> @@ -215,6 +219,8 @@ import { isEnabledUrlPreview } from '@/instance.js'; import { type Keymap } from '@/scripts/hotkey.js'; import { focusPrev, focusNext } from '@/scripts/focus.js'; import { getAppearNote } from '@/scripts/get-appear-note.js'; +import { miLocalStorage } from '@/local-storage.js'; +import detectLanguage from '@/scripts/detect-language.js'; const props = withDefaults(defineProps<{ note: Misskey.entities.Note; @@ -572,6 +578,23 @@ async function clip(): Promise<void> { os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus); } +const isForeignLanguage: boolean = appearNote.value.text != null && (() => { + const targetLang = (miLocalStorage.getItem('lang') ?? navigator.language).slice(0, 2); + const postLang = detectLanguage(appearNote.value.text); + return postLang !== '' && postLang !== targetLang; +})(); + +async function translate(): Promise<void> { + if (props.translation.value != null) return; + 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; +} + function showRenoteMenu(): void { if (props.mock) { return; diff --git a/packages/frontend/src/scripts/detect-language.ts b/packages/frontend/src/scripts/detect-language.ts new file mode 100644 index 0000000000..ff40779a02 --- /dev/null +++ b/packages/frontend/src/scripts/detect-language.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { detect } from 'tinyld'; +import * as mfm from 'mfm-js'; + +export default function detectLanguage(text: string): string { + const nodes = mfm.parse(text); + const filtered = mfm.extract(nodes, (node) => { + return node.type === 'text' || node.type === 'quote'; + }); + const purified = mfm.toString(filtered); + + if (detect(purified) === '') return 'en'; + return detect(purified); +} diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 504562a91e..610b1702e5 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -27,6 +27,13 @@ const externalPackages = [ : id; }, }, + { + name: 'tinyld', + match: /^tinyld$/, + path(): string { + return `https://cdn.jsdelivr.net/npm/tinyld@${packageInfo.dependencies.tinyld}/dist/tinyld.normal.node.mjs` + }, + }, ]; const hash = (str: string, seed = 0): number => {