enhance(frontend): 클라이언트 언어와 노트 본문의 언어가 같으면 번역 버튼을 표시하지 않음

This commit is contained in:
NoriDev 2023-11-29 17:25:45 +09:00
parent e4c291f815
commit e54651acfd
8 changed files with 66 additions and 21 deletions

View file

@ -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: 열람 주의로 설정된 노트의 반응이 더 보기를 눌러야 표시됨

View file

@ -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",

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View 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);
}

View file

@ -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'}