diff --git a/locales/en-US.yml b/locales/en-US.yml index fa3c4af251..46242d5fc1 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -675,7 +675,7 @@ emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP verif smtpSecure: "Use implicit SSL/TLS for SMTP connections" smtpSecureInfo: "Turn this off when using STARTTLS" testEmail: "Test email delivery" -wordMute: "Word mute" +wordMute: "Word and language mutes" regexpError: "Regular Expression error" regexpErrorDescription: "An error occurred in the regular expression on line {line} of your {tab} word mutes:" @@ -1375,14 +1375,19 @@ _menuDisplay: hide: "Hide" _wordMute: muteWords: "Muted words" + muteLangs: "Muted Languages" muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition." muteWordsDescription2: "Surround keywords with slashes to use regular expressions." + muteLangsDescription: "Separate with spaces or line breaks for an OR condition." + muteLangsDescription2: "Use language code e.g. en, fr, ja, zh." softDescription: "Hide posts that fulfil the set conditions from the timeline." + langDescription: "Hide posts that match set language from the timeline." hardDescription: "Prevents posts fulfilling the set conditions from being added to the timeline. In addition, these posts will not be added to the timeline even if the conditions are changed." soft: "Soft" + lang: "Language" hard: "Hard" mutedNotes: "Muted posts" _instanceMute: diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 4cabff1d04..9c78e1de52 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1200,11 +1200,16 @@ _menuDisplay: hide: "隠す" _wordMute: muteWords: "ミュートするワード" + muteLangs: "ミュートされた言語" muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。" muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。" + muteLangsDescription: "OR 条件の場合はスペースまたは改行で区切ります。" + muteLangsDescription2: "言語コードを使用します。例: en, fr, ja, zh." softDescription: "指定した条件の投稿をタイムラインから隠します。" + langDescription: "設定した言語に一致する投稿をタイムラインから非表示にします。" hardDescription: "指定した条件の投稿をタイムラインに追加しないようにします。追加されなかった投稿は、条件を変更しても除外されたままになります。" soft: "ソフト" + lang: "言語" hard: "ハード" mutedNotes: "ミュートされた投稿" _instanceMute: diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index cd18d6ce2e..66983ae464 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1110,11 +1110,16 @@ _menuDisplay: hide: "隐藏" _wordMute: muteWords: "过滤词" + muteLangs: "过滤语言" muteWordsDescription: "AND 条件用空格分隔,OR 条件用换行符分隔。" muteWordsDescription2: "将关键字用斜线括起来表示正则表达式。" + muteLangsDescription: "OR 条件用空格,换行符分隔" + muteLangsDescription2: "使用语言代码。例: en, fr, ja, zh." softDescription: "隐藏时间线中指定条件的帖子。" + langDescription: "从时间线中隐藏与设置语言匹配的帖子。" hardDescription: "防止将具有指定条件的帖子添加到时间线。 即使您更改条件,原先未添加的帖文也会被排除在外。" soft: "软过滤" + lang: "语言" hard: "硬过滤" mutedNotes: "已过滤的帖子" _instanceMute: diff --git a/package.json b/package.json index b5bd75f506..4f381e0689 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firefish", - "version": "1.0.5-dev13", + "version": "1.0.5-dev14", "codename": "aqua", "repository": { "type": "git", diff --git a/packages/backend/package.json b/packages/backend/package.json index 00db1887ad..4e648ad30a 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -133,6 +133,7 @@ "tar-stream": "^3.1.6", "tesseract.js": "^4.1.1", "tinycolor2": "1.6.0", + "tinyld": "^1.3.4", "tmp": "0.2.1", "twemoji-parser": "14.0.0", "typeorm": "0.3.17", @@ -144,7 +145,7 @@ }, "devDependencies": { "@swc/cli": "^0.1.62", - "@swc/core": "^1.3.75", + "@swc/core": "1.3.82", "@types/adm-zip": "^0.5.0", "@types/bcryptjs": "2.4.2", "@types/escape-regexp": "0.0.1", diff --git a/packages/backend/src/@types/langdetect.d.ts b/packages/backend/src/@types/langdetect.d.ts new file mode 100644 index 0000000000..bcfa6f5ca4 --- /dev/null +++ b/packages/backend/src/@types/langdetect.d.ts @@ -0,0 +1,7 @@ +declare module "langdetect" { + interface DetectResult { + lang: string; + prob: number; + } + export function detect(words: string): DetectResult[]; +} diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index a65df2b582..ee5b179760 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -38,6 +38,7 @@ import { } from "@/db/scylla.js"; import { LocalFollowingsCache } from "@/misc/cache.js"; import { userByIdCache } from "@/services/user-cache.js"; +import { detect as detectLanguage_ } from "tinyld"; export async function populatePoll( note: Note | ScyllaNote, @@ -302,6 +303,8 @@ export const NoteRepository = db.getRepository(Note).extend({ note.emojis.concat(reactionEmojiNames), host, ); + + const lang = detectLanguage_(`${note.cw ?? ''}\n${note.text ?? ''}`) ?? "unknown" const reactionEmoji = await populateEmojis(reactionEmojiNames, host); const packed: Packed<"Note"> = await awaitAll({ id: note.id, @@ -376,6 +379,7 @@ export const NoteRepository = db.getRepository(Note).extend({ : undefined, } : {}), + lang: lang, }); if (packed.user.isCat && packed.user.speakAsCat && packed.text) { diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index c95aba45f4..2722736530 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -193,8 +193,12 @@ export default define(meta, paramDef, async (ps, user) => { .andWhere( new Brackets((qb) => { qb.where("note.userId = :meId", { meId: user.id }); - if (hasFollowing) - qb.orWhere(`note.userId IN (${followingQuery.getQuery()})`); + if (hasFollowing) { + qb.orWhere( + `note.userId IN (${followingQuery.getQuery()})`, + followingQuery.getParameters(), + ); + } }), ) .leftJoinAndSelect("note.reply", "reply") diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue index 77c9e18561..9b7dc9fdba 100644 --- a/packages/client/src/components/MkNote.vue +++ b/packages/client/src/components/MkNote.vue @@ -354,7 +354,12 @@ const isMyRenote = $i && $i.id === note.value.userId; const showContent = ref(false); const isDeleted = ref(false); const muted = ref( - getWordSoftMute(note.value, $i, defaultStore.state.mutedWords), + getWordSoftMute( + note.value, + $i, + defaultStore.state.mutedWords, + defaultStore.state.mutedLangs, + ), ); const translation = ref(null); const translating = ref(false); diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index 9da05073ab..f27a3d5b2e 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -210,7 +210,12 @@ const reactButton = ref(); const showContent = ref(false); const isDeleted = ref(false); const muted = ref( - getWordSoftMute(note.value, $i, defaultStore.state.mutedWords), + getWordSoftMute( + note.value, + $i, + defaultStore.state.mutedWords, + defaultStore.state.mutedLangs, + ), ); const translation = ref(null); const translating = ref(false); diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue index 7deea9646c..99dc6e6521 100644 --- a/packages/client/src/components/MkNoteSub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -266,7 +266,12 @@ const appearNote = computed(() => ); const isDeleted = ref(false); const muted = ref( - getWordSoftMute(note.value, $i, defaultStore.state.mutedWords), + getWordSoftMute( + note.value, + $i, + defaultStore.state.mutedWords, + defaultStore.state.mutedLangs, + ), ); const translation = ref(null); const translating = ref(false); diff --git a/packages/client/src/components/MkPostForm.vue b/packages/client/src/components/MkPostForm.vue index 8f8108f116..51ce6e1b95 100644 --- a/packages/client/src/components/MkPostForm.vue +++ b/packages/client/src/components/MkPostForm.vue @@ -29,7 +29,7 @@ >{{ maxTextLength - textLength }}