diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index efeeb88960..1e1100ac6c 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -63,6 +63,15 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - Feat: 모바일 환경에서 하단 내비게이션 바를 개인화할 수 있음 - `설정` - `내비게이션 바`의 `하단 내비게이션 바`에서 설정할 수 있습니다. - Feat: 리버시 대전 중에 상대방에게 리액션을 보낼 수 있음 (misskey-dev/misskey#13119) +- Feat: 노트 자동 번역 기능 + - 노트 자동 번역은 번역 서비스의 API 제한을 방지하기 위해 자동으로 활성화되지 않으며, 기본적으로 비활성화되어 있습니다. + - `역할`에서 `자동 번역 기능 이용 가능 여부`를 활성화 하면 자동 번역을 사용할 수 있는 상태가 됩니다. + - 이후, 각 사용자별로 `설정` - `일반`에서 `노트 자동 번역`을 활성화한 사용자는 자동 번역을 사용할 수 있습니다. + - 노트가 아래와 같이 설정된 경우에는 노트 자동 번역을 사용하지 않습니다. + - 노트가 `내용 가리기`로 설정되어 있음 + - 노트의 내용이 긺 + - 노트에 파일이 포함되어 있음 + - `자동 번역 기능 이용 가능 여부` 역할의 권한을 상실하게 되면 모든 사용자의 `노트 자동 번역` 설정도 자동으로 비활성화 됩니다. ### Client - Enhance: CherryPick 업데이트 페이지를 제어판 목록에 추가함 @@ -88,6 +97,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - Fix: 이미지 자르기를 할 때 이미지 크기 전체를 표시하지 못할 수 있음 - Fix: 페이지에서 페이지 생성 버튼이 본문에 중복으로 표시됨 - Fix: 노트 본문의 사용자 멘션 영역을 클릭하면 노트 상세 페이지가 표시됨 +- Fix: 역할 권한에 의해 번역 기능을 사용할 수 없을 때도 번역 버튼이 표시됨 ### Server - Enhance: 보안 향상을 위해 비밀번호 해싱 알고리즘이 `bcrypt`에서 `argon2`로 변경됨 (kokonect-link/cherrypick#511), (1673beta/cherrypick#88) diff --git a/locales/en-US.yml b/locales/en-US.yml index 902b35f077..d8c740bbf6 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1,5 +1,7 @@ --- _lang_: "English" +useAutoTranslate: "Automatic translation of notes" +useAutoTranslateDescription: "The server administrator has disabled this feature.\nContact your server administrator to use the feature." widgets: "Widgets" postNote: "Post note" bottomNavbar: "Bottom navigation bar" diff --git a/locales/index.d.ts b/locales/index.d.ts index 84a08564ab..d022882abd 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -13,6 +13,15 @@ export interface Locale extends ILocale { * 日本語 */ "_lang_": string; + /** + * ノートを自動翻訳 + */ + "useAutoTranslate": string; + /** + * サーバー管理者がこの機能を無効にしました。 + * 機能を使用するには、サーバー管理者にお問い合わせください。 + */ + "useAutoTranslateDescription": string; /** * ウィジェット */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 7dfe486332..7b4784299a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1,5 +1,7 @@ _lang_: "日本語" +useAutoTranslate: "ノートを自動翻訳" +useAutoTranslateDescription: "サーバー管理者がこの機能を無効にしました。\n機能を使用するには、サーバー管理者にお問い合わせください。" widgets: "ウィジェット" postNote: "ノートを作成" bottomNavbar: "下のナビゲーションバー" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index c085f31509..027c4a43df 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1,5 +1,7 @@ --- _lang_: "한국어" +useAutoTranslate: "노트 자동 번역" +useAutoTranslateDescription: "서버 관리자가 이 기능을 사용할 수 없도록 설정했어요.\n기능을 사용하려면 서버 관리자에게 문의해 주세요." widgets: "위젯" postNote: "노트 작성" bottomNavbar: "하단 내비게이션 바" @@ -1991,6 +1993,7 @@ _role: canHideAds: "광고 숨기기" canSearchNotes: "노트 검색 이용 가능 여부" canUseTranslator: "번역 기능 이용 가능 여부" + canUseAutoTranslate: "자동 번역 기능 이용 가능 여부" avatarDecorationLimit: "최대로 붙일 수 있는 아바타 장식 개수" canImportAntennas: "안테나 가져오기 허용" canImportBlocking: "차단 목록 가져오기 허용" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index f7d0b4025d..c8c8a64c4b 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -45,6 +45,7 @@ export type RolePolicies = { canManageAvatarDecorations: boolean; canSearchNotes: boolean; canUseTranslator: boolean; + canUseAutoTranslate: boolean; canHideAds: boolean; driveCapacityMb: number; alwaysMarkNsfw: boolean; @@ -80,6 +81,7 @@ export const DEFAULT_POLICIES: RolePolicies = { canManageAvatarDecorations: false, canSearchNotes: false, canUseTranslator: true, + canUseAutoTranslate: false, canHideAds: false, driveCapacityMb: 100, alwaysMarkNsfw: false, @@ -386,6 +388,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { canManageAvatarDecorations: calc('canManageAvatarDecorations', vs => vs.some(v => v === true)), canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)), canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)), + canUseAutoTranslate: calc('canUseAutoTranslate', vs => vs.some(v => v === true)), canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)), alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)), diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index d52896d0ba..84d8e2a79c 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -216,6 +216,10 @@ export const packedRolePoliciesSchema = { type: 'boolean', optional: false, nullable: false, }, + canUseAutoTranslate: { + type: 'boolean', + optional: false, nullable: false, + }, canHideAds: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/cherrypick-js/src/autogen/types.ts b/packages/cherrypick-js/src/autogen/types.ts index 4044484940..3dd0e714c7 100644 --- a/packages/cherrypick-js/src/autogen/types.ts +++ b/packages/cherrypick-js/src/autogen/types.ts @@ -5174,6 +5174,7 @@ export type components = { canManageAvatarDecorations: boolean; canSearchNotes: boolean; canUseTranslator: boolean; + canUseAutoTranslate: boolean; canHideAds: boolean; driveCapacityMb: number; alwaysMarkNsfw: boolean; diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index d7f90bfa80..5b56d97870 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -88,6 +88,7 @@ export const ROLE_POLICIES = [ 'canManageAvatarDecorations', 'canSearchNotes', 'canUseTranslator', + 'canUseAutoTranslate', 'canHideAds', 'driveCapacityMb', 'alwaysMarkNsfw', diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 864df87476..ecda25d338 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -92,7 +92,7 @@ SPDX-License-Identifier: AGPL-3.0-only :enableEmojiMenu="true" :enableEmojiMenuReaction="true" /> -
+
@@ -741,6 +741,9 @@ const isForeignLanguage: boolean = appearNote.value.text != null && (() => { return postLang !== '' && postLang !== targetLang; })(); +if (defaultStore.state.useAutoTranslate && !$i.policies.canUseAutoTranslate) defaultStore.set('useAutoTranslate', false); +if ($i.policies.canUseTranslator && defaultStore.state.useAutoTranslate && !isLong && appearNote.value.text && isForeignLanguage) translate(); + async function translate(): Promise { if (translation.value != null) return; translating.value = true; diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index a88ec1e9ab..3bb070a5e3 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -115,7 +115,7 @@ SPDX-License-Identifier: AGPL-3.0-only :enableEmojiMenuReaction="true" /> RN: -
+
@@ -681,6 +681,9 @@ const isForeignLanguage: boolean = appearNote.value.text != null && (() => { return postLang !== '' && postLang !== targetLang; })(); +if (defaultStore.state.useAutoTranslate && !$i.policies.canUseAutoTranslate) defaultStore.set('useAutoTranslate', false); +if ($i.policies.canUseTranslator && defaultStore.state.useAutoTranslate && appearNote.value.text && isForeignLanguage) translate(); + async function translate(): Promise { if (translation.value != null) return; translating.value = true; diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index f6f7821795..096c576175 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only :enableEmojiMenuReaction="true" /> RN: ... -
+
@@ -441,6 +441,9 @@ const isForeignLanguage: boolean = note.value.text != null && (() => { return postLang !== '' && postLang !== targetLang; })(); +if (defaultStore.state.useAutoTranslate && !$i.policies.canUseAutoTranslate) defaultStore.set('useAutoTranslate', false); +if ($i.policies.canUseTranslator && defaultStore.state.useAutoTranslate && !isLong && note.value.text && isForeignLanguage) translate(); + async function translate(): Promise { if (translation.value != null) return; translating.value = true; diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 88979322de..7e77bc1f3f 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -358,6 +358,26 @@ SPDX-License-Identifier: AGPL-3.0-only
+ + + +
+ + + + + + + + + +
+
+