enhance(frontend): 클라이언트 언어와 노트 본문의 언어가 같으면 번역 버튼을 표시하지 않음
This commit is contained in:
parent
e4c291f815
commit
e54651acfd
|
@ -46,6 +46,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2023xx](CHANGE
|
|||
- Enhance: 사운드 설정을 기본값으로 복원하거나 저장할 때 확실하게 표시함
|
||||
- Enhance: 리모트 서버와 동일한 이모지가 존재하지 않는 경우 '이모지 복사'를 비활성화함
|
||||
- Enhance: 아이콘 장식을 바로 업로드 하거나 드라이브에서 불러올 수 있음 ([Secineralyr/misskey.dream@e358212d](https://github.com/Secineralyr/misskey.dream/commit/e358212da93256749e31d9e0ca9dd2ed37fd548e), [Secineralyr/misskey.dream@52592fea](https://github.com/Secineralyr/misskey.dream/commit/52592fea52684497ba7e07f173aac2b1083afcb1))
|
||||
- Enhance: 클라이언트 언어와 노트 본문의 언어가 같으면 번역 버튼을 표시하지 않음
|
||||
- Fix: '모달 배경색 제거' 옵션이 이모지 피커에 반영되지 않음
|
||||
- Fix: 열람 주의로 설정된 노트의 반응이 더 보기를 눌러야 표시됨
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"three": "0.158.0",
|
||||
"throttle-debounce": "5.0.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tinyld": "^1.3.4",
|
||||
"tsc-alias": "1.8.8",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"twemoji-parser": "14.0.0",
|
||||
|
|
|
@ -85,7 +85,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
/>
|
||||
<div v-if="defaultStore.state.showTranslateButtonInNote && instance.translatorAvailable && $i && appearNote.text" style="padding-top: 5px; color: var(--accent);">
|
||||
<div v-if="defaultStore.state.showTranslateButtonInNote && instance.translatorAvailable && $i && appearNote.text && isForeignLanguage" style="padding-top: 5px; color: var(--accent);">
|
||||
<button v-if="!(translating || translation)" ref="translateButton" class="_button" @mousedown="translate()">{{ i18n.ts.translateNote }}</button>
|
||||
<button v-else class="_button" @mousedown="translation = null">{{ i18n.ts.close }}</button>
|
||||
</div>
|
||||
|
@ -239,6 +239,7 @@ import { miLocalStorage } from '@/local-storage.js';
|
|||
import { instance } from '@/instance.js';
|
||||
import { concat } from '@/scripts/array.js';
|
||||
import { vibrate } from '@/scripts/vibrate.js';
|
||||
import detectLanguage from '@/scripts/detect-language.js';
|
||||
|
||||
let showEl = $ref(false);
|
||||
|
||||
|
@ -592,6 +593,12 @@ async function clip() {
|
|||
os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||
}
|
||||
|
||||
const isForeignLanguage: boolean = appearNote.text != null && (() => {
|
||||
const targetLang = (miLocalStorage.getItem('lang') ?? navigator.language).slice(0, 2);
|
||||
const postLang = detectLanguage(appearNote.text);
|
||||
return postLang !== '' && postLang !== targetLang;
|
||||
})();
|
||||
|
||||
async function translate(): Promise<void> {
|
||||
if (translation.value != null) return;
|
||||
translating.value = true;
|
||||
|
|
|
@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:enableEmojiMenuReaction="true"
|
||||
/>
|
||||
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
|
||||
<div v-if="defaultStore.state.showTranslateButtonInNote && instance.translatorAvailable && $i && appearNote.text" style="padding-top: 5px; color: var(--accent);">
|
||||
<div v-if="defaultStore.state.showTranslateButtonInNote && instance.translatorAvailable && $i && appearNote.text && isForeignLanguage" style="padding-top: 5px; color: var(--accent);">
|
||||
<button v-if="!(translating || translation)" ref="translateButton" class="_button" @mousedown="translate()">{{ i18n.ts.translateNote }}</button>
|
||||
<button v-else class="_button" @mousedown="translation = null">{{ i18n.ts.close }}</button>
|
||||
</div>
|
||||
|
@ -315,6 +315,7 @@ import { infoImageUrl, instance } from '@/instance.js';
|
|||
import MkPostForm from '@/components/MkPostFormSimple.vue';
|
||||
import { deviceKind } from '@/scripts/device-kind.js';
|
||||
import { vibrate } from '@/scripts/vibrate.js';
|
||||
import detectLanguage from '@/scripts/detect-language.js';
|
||||
|
||||
const MOBILE_THRESHOLD = 500;
|
||||
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
|
||||
|
@ -601,6 +602,12 @@ function menu(viaKeyboard = false): void {
|
|||
}).then(focus).finally(cleanup);
|
||||
}
|
||||
|
||||
const isForeignLanguage: boolean = appearNote.text != null && (() => {
|
||||
const targetLang = (miLocalStorage.getItem('lang') ?? navigator.language).slice(0, 2);
|
||||
const postLang = detectLanguage(appearNote.text);
|
||||
return postLang !== '' && postLang !== targetLang;
|
||||
})();
|
||||
|
||||
async function translate(): Promise<void> {
|
||||
if (translation.value != null) return;
|
||||
translating.value = true;
|
||||
|
|
|
@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:enableEmojiMenuReaction="true"
|
||||
/>
|
||||
<MkA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
||||
<div v-if="defaultStore.state.showTranslateButtonInNote && instance.translatorAvailable && $i && note.text" style="padding-top: 5px; color: var(--accent);">
|
||||
<div v-if="defaultStore.state.showTranslateButtonInNote && instance.translatorAvailable && $i && note.text && isForeignLanguage" style="padding-top: 5px; color: var(--accent);">
|
||||
<button v-if="!(translating || translation)" ref="translateButton" class="_button" @mousedown="translate()">{{ i18n.ts.translateNote }}</button>
|
||||
<button v-else class="_button" @mousedown="translation = null">{{ i18n.ts.close }}</button>
|
||||
</div>
|
||||
|
@ -143,6 +143,7 @@ import { claimAchievement } from '@/scripts/achievements.js';
|
|||
import { useNoteCapture } from '@/scripts/use-note-capture.js';
|
||||
import { concat } from '@/scripts/array.js';
|
||||
import { vibrate } from '@/scripts/vibrate.js';
|
||||
import detectLanguage from '@/scripts/detect-language.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
@ -403,6 +404,12 @@ async function clip() {
|
|||
os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||
}
|
||||
|
||||
const isForeignLanguage: boolean = note.text != null && (() => {
|
||||
const targetLang = (miLocalStorage.getItem('lang') ?? navigator.language).slice(0, 2);
|
||||
const postLang = detectLanguage(note.text);
|
||||
return postLang !== '' && postLang !== targetLang;
|
||||
})();
|
||||
|
||||
async function translate(): Promise<void> {
|
||||
if (translation.value != null) return;
|
||||
translating.value = true;
|
||||
|
|
|
@ -80,7 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkOmit>
|
||||
<Mfm v-if="user.description" :text="user.description" :isNote="false" :author="user"/>
|
||||
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
|
||||
<div v-if="user.description">
|
||||
<div v-if="user.description && isForeignLanguage">
|
||||
<MkButton v-if="!(translating || translation)" class="translateButton" small @click="translate"><i class="ti ti-language-hiragana"></i> {{ i18n.ts.translateProfile }}</MkButton>
|
||||
<MkButton v-else class="translateButton" small @click="translation = null"><i class="ti ti-x"></i> {{ i18n.ts.close }}</MkButton>
|
||||
</div>
|
||||
|
@ -187,6 +187,7 @@ import { defaultStore } from '@/store.js';
|
|||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { editNickname } from '@/scripts/edit-nickname.js';
|
||||
import { vibrate } from '@/scripts/vibrate.js';
|
||||
import detectLanguage from '@/scripts/detect-language.js';
|
||||
|
||||
function calcAge(birthdate: string): number {
|
||||
const date = new Date(birthdate);
|
||||
|
@ -290,6 +291,12 @@ async function updateMemo() {
|
|||
isEditingMemo = false;
|
||||
}
|
||||
|
||||
const isForeignLanguage: boolean = user.description != null && (() => {
|
||||
const targetLang = (miLocalStorage.getItem('lang') ?? navigator.language).slice(0, 2);
|
||||
const postLang = detectLanguage(user.description);
|
||||
return postLang !== '' && postLang !== targetLang;
|
||||
})();
|
||||
|
||||
async function translate(): Promise<void> {
|
||||
if (translation.value != null) return;
|
||||
translating.value = true;
|
||||
|
|
13
packages/frontend/src/scripts/detect-language.ts
Normal file
13
packages/frontend/src/scripts/detect-language.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { detect } from 'tinyld';
|
||||
import * as mfm from 'cherrypick-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);
|
||||
}
|
|
@ -878,6 +878,9 @@ importers:
|
|||
tinycolor2:
|
||||
specifier: 1.6.0
|
||||
version: 1.6.0
|
||||
tinyld:
|
||||
specifier: ^1.3.4
|
||||
version: 1.3.4
|
||||
tsc-alias:
|
||||
specifier: 1.8.8
|
||||
version: 1.8.8
|
||||
|
@ -962,10 +965,10 @@ importers:
|
|||
version: 7.5.3
|
||||
'@storybook/vue3':
|
||||
specifier: 7.5.3
|
||||
version: 7.5.3(@vue/compiler-core@3.3.8)(vue@3.3.9)
|
||||
version: 7.5.3(@vue/compiler-core@3.3.9)(vue@3.3.9)
|
||||
'@storybook/vue3-vite':
|
||||
specifier: 7.5.3
|
||||
version: 7.5.3(@vue/compiler-core@3.3.8)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2)(vite@5.0.2)(vue@3.3.9)
|
||||
version: 7.5.3(@vue/compiler-core@3.3.9)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2)(vite@5.0.2)(vue@3.3.9)
|
||||
'@testing-library/vue':
|
||||
specifier: 8.0.1
|
||||
version: 8.0.1(@vue/compiler-sfc@3.3.9)(vue@3.3.9)
|
||||
|
@ -7158,7 +7161,7 @@ packages:
|
|||
file-system-cache: 2.3.0
|
||||
dev: true
|
||||
|
||||
/@storybook/vue3-vite@7.5.3(@vue/compiler-core@3.3.8)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2)(vite@5.0.2)(vue@3.3.9):
|
||||
/@storybook/vue3-vite@7.5.3(@vue/compiler-core@3.3.9)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2)(vite@5.0.2)(vue@3.3.9):
|
||||
resolution: {integrity: sha512-gkNwDDn2AKthAtaoPrHb0+2gi33UluxpfSq/M5COoMEVFphj6y/jyDa+OEYlceXgnD8g2xvX4/yv2TbTNDzmcQ==}
|
||||
engines: {node: ^14.18 || >=16}
|
||||
peerDependencies:
|
||||
|
@ -7168,7 +7171,7 @@ packages:
|
|||
dependencies:
|
||||
'@storybook/builder-vite': 7.5.3(typescript@5.3.2)(vite@5.0.2)
|
||||
'@storybook/core-server': 7.5.3
|
||||
'@storybook/vue3': 7.5.3(@vue/compiler-core@3.3.8)(vue@3.3.9)
|
||||
'@storybook/vue3': 7.5.3(@vue/compiler-core@3.3.9)(vue@3.3.9)
|
||||
'@vitejs/plugin-vue': 4.5.0(vite@5.0.2)(vue@3.3.9)
|
||||
magic-string: 0.30.5
|
||||
react: 18.2.0
|
||||
|
@ -7187,7 +7190,7 @@ packages:
|
|||
- vue
|
||||
dev: true
|
||||
|
||||
/@storybook/vue3@7.5.3(@vue/compiler-core@3.3.8)(vue@3.3.9):
|
||||
/@storybook/vue3@7.5.3(@vue/compiler-core@3.3.9)(vue@3.3.9):
|
||||
resolution: {integrity: sha512-JaxtOl3UD9YhPrOqHuKtpqHMnFril3sBUxx/no2yM/mZYmNpAVd/C6PFM839WCay1mAywPuUoebJvmwWxWijkw==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
peerDependencies:
|
||||
|
@ -7199,7 +7202,7 @@ packages:
|
|||
'@storybook/global': 5.0.0
|
||||
'@storybook/preview-api': 7.5.3
|
||||
'@storybook/types': 7.5.3
|
||||
'@vue/compiler-core': 3.3.8
|
||||
'@vue/compiler-core': 3.3.9
|
||||
lodash: 4.17.21
|
||||
ts-dedent: 2.2.0
|
||||
type-fest: 2.19.0
|
||||
|
@ -10134,7 +10137,7 @@ packages:
|
|||
normalize-path: 3.0.0
|
||||
readdirp: 3.6.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
fsevents: 2.3.3
|
||||
|
||||
/chownr@1.1.4:
|
||||
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||
|
@ -12454,13 +12457,6 @@ packages:
|
|||
/fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
/fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
|
@ -14104,7 +14100,7 @@ packages:
|
|||
micromatch: 4.0.5
|
||||
walker: 1.0.8
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/jest-leak-detector@29.7.0:
|
||||
|
@ -18066,7 +18062,7 @@ packages:
|
|||
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/rollup@4.6.0:
|
||||
|
@ -18086,7 +18082,7 @@ packages:
|
|||
'@rollup/rollup-win32-arm64-msvc': 4.6.0
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.6.0
|
||||
'@rollup/rollup-win32-x64-msvc': 4.6.0
|
||||
fsevents: 2.3.2
|
||||
fsevents: 2.3.3
|
||||
|
||||
/rrweb-cssom@0.6.0:
|
||||
resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
|
||||
|
@ -19262,6 +19258,12 @@ packages:
|
|||
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
||||
dev: false
|
||||
|
||||
/tinyld@1.3.4:
|
||||
resolution: {integrity: sha512-u26CNoaInA4XpDU+8s/6Cq8xHc2T5M4fXB3ICfXPokUQoLzmPgSZU02TAkFwFMJCWTjk53gtkS8pETTreZwCqw==}
|
||||
engines: {node: '>= 12.10.0', npm: '>= 6.12.0', yarn: '>= 1.20.0'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/tinypool@0.7.0:
|
||||
resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
|
Loading…
Reference in a new issue