Merge branch 'language' into 'develop'

Add language picker to post form

Co-authored-by: sup39 <dev@sup39.dev>
Co-authored-by: Essem <smswessem@gmail.com>

Closes #9692

See merge request firefish/firefish!10616
This commit is contained in:
naskya 2024-02-06 04:23:56 +00:00
commit ddde16cfdf
20 changed files with 476 additions and 797 deletions

View file

@ -1156,6 +1156,8 @@ detectPostLanguage: "Automatically detect the language and show a translate butt
vibrate: "Play vibrations"
openServerInfo: "Show server information by clicking the server ticker on a post"
iconSet: "Icon set"
suggested: "Suggested"
noLanguage: "No language"
useEmojiCdn: "Get Twemoji from CDN"
useEmojiCdnDescription: "Use Twemoji from the JSDelivr CDN instead of the server's assets."

View file

@ -2005,5 +2005,7 @@ _iconSets:
regular: "標準"
fill: "塗りつぶし"
duotone: "2色"
suggested: "候補"
noLanguage: "言語なし"
moreUrls: "固定するページ"
moreUrlsDescription: "左下のヘルプメニューに固定したいページを以下の形式で、改行区切りで入力してください:\n\"表示名\": https://example.com/"

View file

@ -127,7 +127,6 @@
"tar-stream": "^3.1.6",
"tesseract.js": "^5.0.3",
"tinycolor2": "1.6.0",
"tinyld": "^1.3.4",
"tmp": "0.2.1",
"typeorm": "0.3.17",
"ulid": "2.3.0",

View file

@ -1,11 +0,0 @@
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);
return detect(purified);
}

View file

@ -1,217 +1,72 @@
// TODO: sharedに置いてフロントエンドのと統合したい
export const langmap = {
ach: {
nativeName: "Lwo",
},
ady: {
nativeName: "Адыгэбзэ",
},
// TODO: sharedに置いてバックエンドのと統合したい
export const iso639Langs1 = {
af: {
nativeName: "Afrikaans",
},
"af-NA": {
nativeName: "Afrikaans (Namibia)",
},
"af-ZA": {
nativeName: "Afrikaans (South Africa)",
},
ak: {
nativeName: "Tɕɥi",
},
ar: {
nativeName: "العربية",
rtl: true,
},
"ar-AR": {
nativeName: "العربية",
},
"ar-MA": {
nativeName: "العربية",
},
"ar-SA": {
nativeName: "العربية (السعودية)",
},
"ay-BO": {
ay: {
nativeName: "Aymar aru",
},
az: {
nativeName: "Azərbaycan dili",
},
"az-AZ": {
nativeName: "Azərbaycan dili",
},
"be-BY": {
be: {
nativeName: "Беларуская",
},
bg: {
nativeName: "Български",
},
"bg-BG": {
nativeName: "Български",
},
bn: {
nativeName: "বাংলা",
},
"bn-IN": {
nativeName: "বাংলা (ভারত)",
},
"bn-BD": {
nativeName: "বাংলা(বাংলাদেশ)",
},
br: {
nativeName: "Brezhoneg",
},
"bs-BA": {
bs: {
nativeName: "Bosanski",
},
ca: {
nativeName: "Català",
},
"ca-ES": {
nativeName: "Català",
},
cak: {
nativeName: "Maya Kaqchikel",
},
"ck-US": {
nativeName: "ᏣᎳᎩ (tsalagi)",
},
cs: {
nativeName: "Čeština",
},
"cs-CZ": {
nativeName: "Čeština",
},
cy: {
nativeName: "Cymraeg",
},
"cy-GB": {
nativeName: "Cymraeg",
},
da: {
nativeName: "Dansk",
},
"da-DK": {
nativeName: "Dansk",
},
de: {
nativeName: "Deutsch",
},
"de-AT": {
nativeName: "Deutsch (Österreich)",
},
"de-DE": {
nativeName: "Deutsch (Deutschland)",
},
"de-CH": {
nativeName: "Deutsch (Schweiz)",
},
dsb: {
nativeName: "Dolnoserbšćina",
},
el: {
nativeName: "Ελληνικά",
},
"el-GR": {
nativeName: "Ελληνικά",
},
en: {
nativeName: "English",
},
"en-GB": {
nativeName: "English (UK)",
},
"en-AU": {
nativeName: "English (Australia)",
},
"en-CA": {
nativeName: "English (Canada)",
},
"en-IE": {
nativeName: "English (Ireland)",
},
"en-IN": {
nativeName: "English (India)",
},
"en-PI": {
nativeName: "English (Pirate)",
},
"en-SG": {
nativeName: "English (Singapore)",
},
"en-UD": {
nativeName: "English (Upside Down)",
},
"en-US": {
nativeName: "English (US)",
},
"en-ZA": {
nativeName: "English (South Africa)",
},
"en@pirate": {
nativeName: "English (Pirate)",
},
eo: {
nativeName: "Esperanto",
},
"eo-EO": {
nativeName: "Esperanto",
},
es: {
nativeName: "Español",
},
"es-AR": {
nativeName: "Español (Argentine)",
},
"es-419": {
nativeName: "Español (Latinoamérica)",
},
"es-CL": {
nativeName: "Español (Chile)",
},
"es-CO": {
nativeName: "Español (Colombia)",
},
"es-EC": {
nativeName: "Español (Ecuador)",
},
"es-ES": {
nativeName: "Español (España)",
},
"es-LA": {
nativeName: "Español (Latinoamérica)",
},
"es-NI": {
nativeName: "Español (Nicaragua)",
},
"es-MX": {
nativeName: "Español (México)",
},
"es-US": {
nativeName: "Español (Estados Unidos)",
},
"es-VE": {
nativeName: "Español (Venezuela)",
},
et: {
nativeName: "eesti keel",
},
"et-EE": {
nativeName: "Eesti (Estonia)",
},
eu: {
nativeName: "Euskara",
},
"eu-ES": {
nativeName: "Euskara",
},
fa: {
nativeName: "فارسی",
},
"fa-IR": {
nativeName: "فارسی",
},
"fb-LT": {
nativeName: "Leet Speak",
rtl: true,
},
ff: {
nativeName: "Fulah",
@ -219,154 +74,86 @@ export const langmap = {
fi: {
nativeName: "Suomi",
},
"fi-FI": {
nativeName: "Suomi",
},
fo: {
nativeName: "Føroyskt",
},
"fo-FO": {
nativeName: "Føroyskt (Færeyjar)",
},
fr: {
nativeName: "Français",
},
"fr-CA": {
nativeName: "Français (Canada)",
},
"fr-FR": {
nativeName: "Français (France)",
},
"fr-BE": {
nativeName: "Français (Belgique)",
},
"fr-CH": {
nativeName: "Français (Suisse)",
},
"fy-NL": {
fy: {
nativeName: "Frysk",
},
ga: {
nativeName: "Gaeilge",
},
"ga-IE": {
nativeName: "Gaeilge",
},
gd: {
nativeName: "Gàidhlig",
},
gl: {
nativeName: "Galego",
},
"gl-ES": {
nativeName: "Galego",
},
"gn-PY": {
gn: {
nativeName: "Avañe'ẽ",
},
"gu-IN": {
gu: {
nativeName: "ગુજરાતી",
},
gv: {
nativeName: "Gaelg",
},
"gx-GR": {
nativeName: "Ἑλληνική ἀρχαία",
},
he: {
nativeName: "עברית‏",
},
"he-IL": {
nativeName: "עברית‏",
rtl: true,
},
hi: {
nativeName: "हिन्दी",
},
"hi-IN": {
nativeName: "हिन्दी",
},
hr: {
nativeName: "Hrvatski",
},
"hr-HR": {
nativeName: "Hrvatski",
},
hsb: {
nativeName: "Hornjoserbšćina",
},
ht: {
nativeName: "Kreyòl",
},
hu: {
nativeName: "Magyar",
},
"hu-HU": {
nativeName: "Magyar",
},
hy: {
nativeName: "Հայերեն",
},
"hy-AM": {
nativeName: "Հայերեն (Հայաստան)",
},
id: {
nativeName: "Bahasa Indonesia",
},
"id-ID": {
nativeName: "Bahasa Indonesia",
},
is: {
nativeName: "Íslenska",
},
"is-IS": {
nativeName: "Íslenska (Iceland)",
},
it: {
nativeName: "Italiano",
},
"it-IT": {
nativeName: "Italiano",
},
ja: {
nativeName: "日本語",
},
"ja-JP": {
nativeName: "日本語 (日本)",
},
"jv-ID": {
jv: {
nativeName: "Basa Jawa",
},
"ka-GE": {
ka: {
nativeName: "ქართული",
},
"kk-KZ": {
kk: {
nativeName: "Қазақша",
},
km: {
nativeName: "ភាសាខ្មែរ",
},
kl: {
nativeName: "kalaallisut",
},
"km-KH": {
km: {
nativeName: "ភាសាខ្មែរ",
},
kab: {
nativeName: "Taqbaylit",
},
kn: {
nativeName: "ಕನ್ನಡ",
},
"kn-IN": {
nativeName: "ಕನ್ನಡ (India)",
},
ko: {
nativeName: "한국어",
},
"ko-KR": {
nativeName: "한국어 (한국)",
},
"ku-TR": {
ku: {
nativeName: "Kurdî",
},
kw: {
@ -375,66 +162,39 @@ export const langmap = {
la: {
nativeName: "Latin",
},
"la-VA": {
nativeName: "Latin",
},
lb: {
nativeName: "Lëtzebuergesch",
},
"li-NL": {
li: {
nativeName: "Lèmbörgs",
},
lt: {
nativeName: "Lietuvių",
},
"lt-LT": {
nativeName: "Lietuvių",
},
lv: {
nativeName: "Latviešu",
},
"lv-LV": {
nativeName: "Latviešu",
},
mai: {
nativeName: "मैथिली, মৈথিলী",
},
"mg-MG": {
mg: {
nativeName: "Malagasy",
},
mk: {
nativeName: "Македонски",
},
"mk-MK": {
nativeName: "Македонски (Македонски)",
},
ml: {
nativeName: "മലയാളം",
},
"ml-IN": {
nativeName: "മലയാളം",
},
"mn-MN": {
mn: {
nativeName: "Монгол",
},
mr: {
nativeName: "मराठी",
},
"mr-IN": {
nativeName: "मराठी",
},
ms: {
nativeName: "Bahasa Melayu",
},
"ms-MY": {
nativeName: "Bahasa Melayu",
},
mt: {
nativeName: "Malti",
},
"mt-MT": {
nativeName: "Malti",
},
my: {
nativeName: "ဗမာစကာ",
},
@ -444,223 +204,179 @@ export const langmap = {
nb: {
nativeName: "Norsk (bokmål)",
},
"nb-NO": {
nativeName: "Norsk (bokmål)",
},
ne: {
nativeName: "नेपाली",
},
"ne-NP": {
nativeName: "नेपाली",
},
nl: {
nativeName: "Nederlands",
},
"nl-BE": {
nativeName: "Nederlands (België)",
},
"nl-NL": {
nativeName: "Nederlands (Nederland)",
},
"nn-NO": {
nn: {
nativeName: "Norsk (nynorsk)",
},
oc: {
nativeName: "Occitan",
},
"or-IN": {
or: {
nativeName: "ଓଡ଼ିଆ",
},
pa: {
nativeName: "ਪੰਜਾਬੀ",
},
"pa-IN": {
nativeName: "ਪੰਜਾਬੀ (ਭਾਰਤ ਨੂੰ)",
},
pl: {
nativeName: "Polski",
},
"pl-PL": {
nativeName: "Polski",
},
"ps-AF": {
ps: {
nativeName: "پښتو",
rtl: true,
},
pt: {
nativeName: "Português",
},
"pt-BR": {
nativeName: "Português (Brasil)",
},
"pt-PT": {
nativeName: "Português (Portugal)",
},
"qu-PE": {
qu: {
nativeName: "Qhichwa",
},
"rm-CH": {
rm: {
nativeName: "Rumantsch",
},
ro: {
nativeName: "Română",
},
"ro-RO": {
nativeName: "Română",
},
ru: {
nativeName: "Русский",
},
"ru-RU": {
nativeName: "Русский",
},
"sa-IN": {
sa: {
nativeName: "संस्कृतम्",
},
"se-NO": {
se: {
nativeName: "Davvisámegiella",
},
sh: {
nativeName: "српскохрватски",
},
"si-LK": {
si: {
nativeName: "සිංහල",
},
sk: {
nativeName: "Slovenčina",
},
"sk-SK": {
nativeName: "Slovenčina (Slovakia)",
},
sl: {
nativeName: "Slovenščina",
},
"sl-SI": {
nativeName: "Slovenščina",
},
"so-SO": {
so: {
nativeName: "Soomaaliga",
},
sq: {
nativeName: "Shqip",
},
"sq-AL": {
nativeName: "Shqip",
},
sr: {
nativeName: "Српски",
},
"sr-RS": {
nativeName: "Српски (Serbia)",
},
su: {
nativeName: "Basa Sunda",
},
sv: {
nativeName: "Svenska",
},
"sv-SE": {
nativeName: "Svenska",
},
sw: {
nativeName: "Kiswahili",
},
"sw-KE": {
nativeName: "Kiswahili",
},
ta: {
nativeName: "தமிழ்",
},
"ta-IN": {
nativeName: "தமிழ்",
},
te: {
nativeName: "తెలుగు",
},
"te-IN": {
nativeName: "తెలుగు",
},
tg: {
nativeName: "забо́ни тоҷикӣ́",
},
"tg-TJ": {
nativeName: "тоҷикӣ",
},
th: {
nativeName: "ภาษาไทย",
},
"th-TH": {
nativeName: "ภาษาไทย (ประเทศไทย)",
},
fil: {
nativeName: "Filipino",
},
tlh: {
nativeName: "tlhIngan-Hol",
},
tr: {
nativeName: "Türkçe",
},
"tr-TR": {
nativeName: "Türkçe",
},
"tt-RU": {
tt: {
nativeName: "татарча",
},
uk: {
nativeName: "Українська",
},
"uk-UA": {
nativeName: "Українська",
},
ur: {
nativeName: "اردو",
},
"ur-PK": {
nativeName: "اردو",
rtl: true,
},
uz: {
nativeName: "O'zbek",
},
"uz-UZ": {
nativeName: "O'zbek",
},
vi: {
nativeName: "Tiếng Việt",
},
"vi-VN": {
nativeName: "Tiếng Việt",
},
"xh-ZA": {
xh: {
nativeName: "isiXhosa",
},
yi: {
nativeName: "ייִדיש",
},
"yi-DE": {
nativeName: "ייִדיש (German)",
rtl: true,
},
zh: {
nativeName: "中文",
},
"zh-Hans": {
nativeName: "中文简体",
},
"zh-Hant": {
nativeName: "中文繁體",
},
"zh-CN": {
nativeName: "中文(中国大陆)",
},
"zh-HK": {
nativeName: "中文(香港)",
},
"zh-SG": {
nativeName: "中文(新加坡)",
},
"zh-TW": {
nativeName: "中文(台灣)",
},
"zu-ZA": {
zu: {
nativeName: "isiZulu",
},
};
export const iso639Langs3 = {
ach: {
nativeName: "Lwo",
},
ady: {
nativeName: "Адыгэбзэ",
},
cak: {
nativeName: "Maya Kaqchikel",
},
chr: {
nativeName: "ᏣᎳᎩ (tsalagi)",
},
dsb: {
nativeName: "Dolnoserbšćina",
},
fil: {
nativeName: "Filipino",
},
hsb: {
nativeName: "Hornjoserbšćina",
},
kab: {
nativeName: "Taqbaylit",
},
mai: {
nativeName: "मैथिली, মৈথিলী",
},
tlh: {
nativeName: "tlhIngan-Hol",
},
tok: {
nativeName: "Toki Pona",
},
yue: {
nativeName: "粵語",
},
nan: {
nativeName: "閩南語",
},
};
export const langmapNoRegion = Object.assign({}, iso639Langs1, iso639Langs3);
export const iso639Regional = {
"zh-hans": {
nativeName: "中文(简体)",
},
"zh-hant": {
nativeName: "中文(繁體)",
},
};
export const langmap = Object.assign({}, langmapNoRegion, iso639Regional);

View file

@ -1,3 +1,5 @@
import { langmap } from "@/misc/langmap.js";
export const packedNoteSchema = {
type: "object",
properties: {
@ -19,6 +21,11 @@ export const packedNoteSchema = {
optional: false,
nullable: true,
},
lang: {
type: "string",
enum: [...Object.keys(langmap)],
nullable: true,
},
cw: {
type: "string",
optional: true,

View file

@ -309,16 +309,13 @@ export async function createNote(
) {
text = note.source.content;
if (note.contentMap != null) {
const key = Object.keys(note.contentMap)[0];
lang = Object.keys(langmap).includes(key)
? key.trim().split("-")[0].split("@")[0]
: null;
const key = Object.keys(note.contentMap)[0].toLowerCase();
lang = Object.keys(langmap).includes(key) ? key : null;
}
} else if (note.contentMap != null) {
const entry = Object.entries(note.contentMap)[0];
lang = Object.keys(langmap).includes(entry[0])
? entry[0].trim().split("-")[0].split("@")[0]
: null;
const key = entry[0].toLowerCase();
lang = Object.keys(langmap).includes(key) ? key : null;
text = htmlToMfm(entry[1], note.tag);
} else if (typeof note.content === "string") {
text = htmlToMfm(note.content, note.tag);
@ -584,15 +581,12 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
text = post.source.content;
if (post.contentMap != null) {
const key = Object.keys(post.contentMap)[0];
lang = Object.keys(langmap).includes(key)
? key.trim().split("-")[0].split("@")[0]
: null;
lang = Object.keys(langmap).includes(key) ? key : null;
}
} else if (post.contentMap != null) {
const entry = Object.entries(post.contentMap)[0];
lang = Object.keys(langmap).includes(entry[0])
? entry[0].trim().split("-")[0].split("@")[0]
: null;
const key = entry[0].toLowerCase();
lang = Object.keys(langmap).includes(key) ? key : null;
text = htmlToMfm(entry[1], post.tag);
} else if (typeof post.content === "string") {
text = htmlToMfm(post.content, post.tag);

View file

@ -6,7 +6,6 @@ import { DriveFiles, Notes, Users, Emojis, Polls } from "@/models/index.js";
import type { Emoji } from "@/models/entities/emoji.js";
import type { Poll } from "@/models/entities/poll.js";
import toHtml from "@/remote/activitypub/misc/get-note-html.js";
import detectLanguage from "@/misc/detect-language.js";
import renderEmoji from "./emoji.js";
import renderMention from "./mention.js";
import renderHashtag from "./hashtag.js";
@ -115,10 +114,9 @@ export default async function renderNote(
}),
);
const lang = note.lang ?? detectLanguage(text);
const contentMap = lang
const contentMap = note.lang
? {
[lang]: content,
[note.lang]: content,
}
: null;

View file

@ -90,7 +90,7 @@ export const paramDef = {
birthday: { ...Users.birthdaySchema, nullable: true },
lang: {
type: "string",
enum: [null, ...Object.keys(langmap)],
enum: Object.keys(langmap),
nullable: true,
},
avatarId: { type: "string", format: "misskey:id", nullable: true },
@ -118,7 +118,7 @@ export const paramDef = {
preventAiLearning: { type: "boolean" },
isBot: { type: "boolean" },
isCat: { type: "boolean" },
speakAsCat: { type: "boolean" },
speakAsCat: { type: "boolean", nullable: true },
isIndexable: { type: "boolean" },
injectFeaturedNote: { type: "boolean" },
receiveAnnouncementEmail: { type: "boolean" },
@ -160,7 +160,7 @@ export default define(meta, paramDef, async (ps, _user, token) => {
if (ps.name !== undefined) updates.name = ps.name;
if (ps.description !== undefined) profileUpdates.description = ps.description;
if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
if (typeof ps.lang === "string") profileUpdates.lang = ps.lang;
if (ps.location !== undefined) profileUpdates.location = ps.location;
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
if (ps.ffVisibility !== undefined)

View file

@ -17,6 +17,7 @@ import { ApiError } from "@/server/api/error.js";
import define from "@/server/api/define.js";
import { HOUR } from "@/const.js";
import { getNote } from "@/server/api/common/getters.js";
import { langmap } from "@/misc/langmap.js";
export const meta = {
tags: ["notes"],
@ -108,7 +109,11 @@ export const paramDef = {
},
},
text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true },
lang: { type: "string", nullable: true, maxLength: 10 },
lang: {
type: "string",
enum: Object.keys(langmap),
nullable: true,
},
cw: { type: "string", nullable: true, maxLength: 100 },
localOnly: { type: "boolean", default: false },
noExtractMentions: { type: "boolean", default: false },

View file

@ -170,7 +170,11 @@ export const paramDef = {
},
},
text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true },
lang: { type: "string", nullable: true, maxLength: 10 },
lang: {
type: "string",
enum: Object.keys(langmap),
nullable: true,
},
cw: { type: "string", nullable: true, maxLength: 250 },
localOnly: { type: "boolean", default: false },
noExtractMentions: { type: "boolean", default: false },
@ -378,11 +382,9 @@ export default define(meta, paramDef, async (ps, user) => {
}
if (ps.lang) {
if (!Object.keys(langmap).includes(ps.lang.trim()))
if (!Object.keys(langmap).includes(ps.lang.toLowerCase()))
throw new Error("invalid param");
ps.lang = ps.lang.trim().split("-")[0].split("@")[0];
} else if (ps.text) {
ps.lang = detectLanguage(ps.text);
ps.lang = ps.lang.toLowerCase();
} else {
ps.lang = null;
}

View file

@ -68,7 +68,6 @@ import meilisearch from "@/db/meilisearch.js";
import { redisClient } from "@/db/redis.js";
import { Mutex } from "redis-semaphore";
import { langmap } from "@/misc/langmap.js";
import detectLanguage from "@/misc/detect-language.js";
const mutedWordsCache = new Cache<
{ userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[]
@ -281,11 +280,9 @@ export default async (
}
if (data.lang) {
if (!Object.keys(langmap).includes(data.lang.trim()))
if (!Object.keys(langmap).includes(data.lang.toLowerCase()))
throw new Error("invalid param");
data.lang = data.lang.trim().split("-")[0].split("@")[0];
} else if (data.text) {
data.lang = detectLanguage(data.text);
data.lang = data.lang.toLowerCase();
} else {
data.lang = null;
}

View file

@ -51,6 +51,21 @@
><i :class="icon('ph-envelope-simple-open')"></i
></span>
</button>
<button
ref="languageButton"
v-tooltip="i18n.ts.language"
class="_button language"
@click="setLanguage"
>
<i
v-if="language === '' || language == null"
class="_button"
:class="icon('ph-seal-warning')"
></i>
<p v-else class="_button" style="font-weight: bold">
{{ language.split("-")[0] }}
</p>
</button>
<button
v-tooltip="i18n.ts.previewNoteText"
class="_button preview"
@ -275,6 +290,9 @@ import { deepClone } from "@/scripts/clone";
import XCheatSheet from "@/components/MkCheatSheetDialog.vue";
import preprocess from "@/scripts/preprocess";
import { vibrate } from "@/scripts/vibrate";
import { langmap } from "@/scripts/langmap";
import { MenuItem } from "@/types/menu";
import detectLanguage from "@/scripts/detect-language";
import icon from "@/scripts/icon";
const modal = inject("modal");
@ -288,6 +306,7 @@ const props = withDefaults(
specified?: firefish.entities.User;
initialText?: string;
initialVisibility?: typeof firefish.noteVisibilities;
initialLanguage?: typeof firefish.languages;
initialFiles?: firefish.entities.DriveFile[];
initialLocalOnly?: boolean;
initialVisibleUsers?: firefish.entities.User[];
@ -315,6 +334,7 @@ const textareaEl = ref<HTMLTextAreaElement | null>(null);
const cwInputEl = ref<HTMLInputElement | null>(null);
const hashtagsInputEl = ref<HTMLInputElement | null>(null);
const visibilityButton = ref<HTMLElement | null>(null);
const languageButton = ref<HTMLElement | undefined>();
const posting = ref(false);
const text = ref(props.initialText ?? "");
@ -340,6 +360,7 @@ const visibility = ref(
: defaultStore.state
.defaultNoteVisibility) as (typeof firefish.noteVisibilities)[number]),
);
const visibleUsers = ref([]);
if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser);
@ -559,6 +580,7 @@ function watchForDraft() {
watch(files, () => saveDraft(), { deep: true });
watch(visibility, () => saveDraft());
watch(localOnly, () => saveDraft());
watch(language, () => saveDraft());
}
function checkMissingMention() {
@ -686,6 +708,105 @@ function setVisibility() {
);
}
const language = ref<string | null>(
props.initialLanguage ??
defaultStore.state.recentlyUsedPostLanguages[0] ??
localStorage.getItem("lang")?.split("-")[0],
);
function filterLangmapByPrefix(
prefix: string,
): { langCode: string; nativeName: string }[] {
let to_return = Object.entries(langmap)
.filter(([langCode, _]) => langCode.startsWith(prefix))
.map(([langCode, v]) => {
return { langCode, nativeName: v.nativeName };
});
if (prefix === "zh")
to_return = to_return.concat([
{ langCode: "yue", nativeName: langmap["yue"].nativeName },
{ langCode: "nan", nativeName: langmap["nan"].nativeName },
]);
return to_return;
}
function setLanguage() {
const actions: Array<MenuItem> = [];
const detectedLanguage: string = detectLanguage(text.value) ?? "";
if (detectedLanguage !== "" && detectedLanguage !== language.value) {
actions.push({
type: "label",
text: i18n.ts.suggested,
});
filterLangmapByPrefix(detectedLanguage).forEach((v) => {
actions.push({
text: v.nativeName,
danger: false,
active: false,
action: () => {
language.value = v.langCode;
},
});
});
actions.push(null);
}
if (language.value != null)
actions.push({
text: langmap[language.value].nativeName,
danger: false,
active: true,
action: () => {},
});
const langs = Object.keys(langmap);
// Show recently used language first
let recentlyUsedLanguagesExist = false;
for (const lang of defaultStore.state.recentlyUsedPostLanguages) {
if (lang === language.value) continue;
if (!langs.includes(lang)) continue;
actions.push({
text: langmap[lang].nativeName,
danger: false,
active: false,
action: () => {
language.value = lang;
},
});
recentlyUsedLanguagesExist = true;
}
if (recentlyUsedLanguagesExist) actions.push(null);
actions.push({
text: i18n.ts.noLanguage,
danger: false,
active: false,
action: () => {
language.value = null;
},
});
for (const lang of langs) {
if (lang === language.value) continue;
if (defaultStore.state.recentlyUsedPostLanguages.includes(lang))
continue;
actions.push({
text: langmap[lang].nativeName,
danger: false,
active: false,
action: () => {
language.value = lang;
},
});
}
os.popupMenu(actions, languageButton.value, {});
}
function pushVisibleUser(user) {
if (
!visibleUsers.value.some(
@ -837,6 +958,7 @@ function saveDraft() {
cw: cw.value,
visibility: visibility.value,
localOnly: localOnly.value,
lang: language.value,
files: files.value,
poll: poll.value,
},
@ -870,6 +992,7 @@ async function post() {
channelId: props.channel ? props.channel.id : undefined,
poll: poll.value,
cw: useCw.value ? cw.value || "" : undefined,
lang: language.value ? language.value : undefined,
localOnly: localOnly.value,
visibility: visibility.value,
visibleUserIds:
@ -936,6 +1059,28 @@ async function post() {
});
});
vibrate([10, 20, 10, 20, 10, 20, 60]);
// update recentlyUsedLanguages
if (language.value != null) {
const languages = Object.keys(langmap);
const maxLength = 6;
defaultStore.set(
"recentlyUsedPostLanguages",
[language.value]
.concat(
defaultStore.state.recentlyUsedPostLanguages.filter(
(lang) => {
return (
lang !== language.value &&
languages.includes(lang)
);
},
),
)
.slice(0, maxLength),
);
}
}
function cancel() {
@ -1026,6 +1171,7 @@ onMounted(() => {
cw.value = draft.data.cw;
visibility.value = draft.data.visibility;
localOnly.value = draft.data.localOnly;
language.value = draft.data.lang;
files.value = (draft.data.files || []).filter(
(draftFile) => draftFile,
);
@ -1052,6 +1198,7 @@ onMounted(() => {
}
visibility.value = init.visibility;
localOnly.value = init.localOnly;
language.value = init.lang;
quoteId.value = init.renote ? init.renote.id : null;
}
@ -1129,6 +1276,11 @@ onMounted(() => {
opacity: 0.7;
}
> .language {
height: 34px;
width: 34px;
}
> .preview {
display: inline-block;
padding: 0;

View file

@ -33,6 +33,7 @@ const props = defineProps<{
specified?: firefish.entities.User;
initialText?: string;
initialVisibility?: typeof firefish.noteVisibilities;
initialLanguage?: typeof firefish.languages;
initialFiles?: firefish.entities.DriveFile[];
initialLocalOnly?: boolean;
initialVisibleUsers?: firefish.entities.User[];

View file

@ -229,16 +229,19 @@ function saveFields() {
});
}
const convertEmptyStringToNull = (x) =>
x === "" ? null : x == null ? undefined : x;
function save() {
os.apiWithDialog("i/update", {
name: profile.name || null,
description: profile.description || null,
location: profile.location || null,
birthday: profile.birthday || null,
lang: profile.lang || null,
name: convertEmptyStringToNull(profile.name),
description: convertEmptyStringToNull(profile.description),
location: convertEmptyStringToNull(profile.location),
birthday: convertEmptyStringToNull(profile.birthday),
lang: convertEmptyStringToNull(profile.lang),
isBot: !!profile.isBot,
isCat: !!profile.isCat,
speakAsCat: !!profile.speakAsCat,
speakAsCat: profile.isCat ? !!profile.speakAsCat : undefined,
});
}

View file

@ -1,217 +1,72 @@
// TODO: sharedに置いてバックエンドのと統合したい
export const langmap = {
ach: {
nativeName: "Lwo",
},
ady: {
nativeName: "Адыгэбзэ",
},
export const iso639Langs1 = {
af: {
nativeName: "Afrikaans",
},
"af-NA": {
nativeName: "Afrikaans (Namibia)",
},
"af-ZA": {
nativeName: "Afrikaans (South Africa)",
},
ak: {
nativeName: "Tɕɥi",
},
ar: {
nativeName: "العربية",
rtl: true,
},
"ar-AR": {
nativeName: "العربية",
},
"ar-MA": {
nativeName: "العربية",
},
"ar-SA": {
nativeName: "العربية (السعودية)",
},
"ay-BO": {
ay: {
nativeName: "Aymar aru",
},
az: {
nativeName: "Azərbaycan dili",
},
"az-AZ": {
nativeName: "Azərbaycan dili",
},
"be-BY": {
be: {
nativeName: "Беларуская",
},
bg: {
nativeName: "Български",
},
"bg-BG": {
nativeName: "Български",
},
bn: {
nativeName: "বাংলা",
},
"bn-IN": {
nativeName: "বাংলা (ভারত)",
},
"bn-BD": {
nativeName: "বাংলা(বাংলাদেশ)",
},
br: {
nativeName: "Brezhoneg",
},
"bs-BA": {
bs: {
nativeName: "Bosanski",
},
ca: {
nativeName: "Català",
},
"ca-ES": {
nativeName: "Català",
},
cak: {
nativeName: "Maya Kaqchikel",
},
"ck-US": {
nativeName: "ᏣᎳᎩ (tsalagi)",
},
cs: {
nativeName: "Čeština",
},
"cs-CZ": {
nativeName: "Čeština",
},
cy: {
nativeName: "Cymraeg",
},
"cy-GB": {
nativeName: "Cymraeg",
},
da: {
nativeName: "Dansk",
},
"da-DK": {
nativeName: "Dansk",
},
de: {
nativeName: "Deutsch",
},
"de-AT": {
nativeName: "Deutsch (Österreich)",
},
"de-DE": {
nativeName: "Deutsch (Deutschland)",
},
"de-CH": {
nativeName: "Deutsch (Schweiz)",
},
dsb: {
nativeName: "Dolnoserbšćina",
},
el: {
nativeName: "Ελληνικά",
},
"el-GR": {
nativeName: "Ελληνικά",
},
en: {
nativeName: "English",
},
"en-GB": {
nativeName: "English (UK)",
},
"en-AU": {
nativeName: "English (Australia)",
},
"en-CA": {
nativeName: "English (Canada)",
},
"en-IE": {
nativeName: "English (Ireland)",
},
"en-IN": {
nativeName: "English (India)",
},
"en-PI": {
nativeName: "English (Pirate)",
},
"en-SG": {
nativeName: "English (Singapore)",
},
"en-UD": {
nativeName: "English (Upside Down)",
},
"en-US": {
nativeName: "English (US)",
},
"en-ZA": {
nativeName: "English (South Africa)",
},
"en@pirate": {
nativeName: "English (Pirate)",
},
eo: {
nativeName: "Esperanto",
},
"eo-EO": {
nativeName: "Esperanto",
},
es: {
nativeName: "Español",
},
"es-AR": {
nativeName: "Español (Argentine)",
},
"es-419": {
nativeName: "Español (Latinoamérica)",
},
"es-CL": {
nativeName: "Español (Chile)",
},
"es-CO": {
nativeName: "Español (Colombia)",
},
"es-EC": {
nativeName: "Español (Ecuador)",
},
"es-ES": {
nativeName: "Español (España)",
},
"es-LA": {
nativeName: "Español (Latinoamérica)",
},
"es-NI": {
nativeName: "Español (Nicaragua)",
},
"es-MX": {
nativeName: "Español (México)",
},
"es-US": {
nativeName: "Español (Estados Unidos)",
},
"es-VE": {
nativeName: "Español (Venezuela)",
},
et: {
nativeName: "eesti keel",
},
"et-EE": {
nativeName: "Eesti (Estonia)",
},
eu: {
nativeName: "Euskara",
},
"eu-ES": {
nativeName: "Euskara",
},
fa: {
nativeName: "فارسی",
},
"fa-IR": {
nativeName: "فارسی",
},
"fb-LT": {
nativeName: "Leet Speak",
rtl: true,
},
ff: {
nativeName: "Fulah",
@ -219,154 +74,86 @@ export const langmap = {
fi: {
nativeName: "Suomi",
},
"fi-FI": {
nativeName: "Suomi",
},
fo: {
nativeName: "Føroyskt",
},
"fo-FO": {
nativeName: "Føroyskt (Færeyjar)",
},
fr: {
nativeName: "Français",
},
"fr-CA": {
nativeName: "Français (Canada)",
},
"fr-FR": {
nativeName: "Français (France)",
},
"fr-BE": {
nativeName: "Français (Belgique)",
},
"fr-CH": {
nativeName: "Français (Suisse)",
},
"fy-NL": {
fy: {
nativeName: "Frysk",
},
ga: {
nativeName: "Gaeilge",
},
"ga-IE": {
nativeName: "Gaeilge",
},
gd: {
nativeName: "Gàidhlig",
},
gl: {
nativeName: "Galego",
},
"gl-ES": {
nativeName: "Galego",
},
"gn-PY": {
gn: {
nativeName: "Avañe'ẽ",
},
"gu-IN": {
gu: {
nativeName: "ગુજરાતી",
},
gv: {
nativeName: "Gaelg",
},
"gx-GR": {
nativeName: "Ἑλληνική ἀρχαία",
},
he: {
nativeName: "עברית‏",
},
"he-IL": {
nativeName: "עברית‏",
rtl: true,
},
hi: {
nativeName: "हिन्दी",
},
"hi-IN": {
nativeName: "हिन्दी",
},
hr: {
nativeName: "Hrvatski",
},
"hr-HR": {
nativeName: "Hrvatski",
},
hsb: {
nativeName: "Hornjoserbšćina",
},
ht: {
nativeName: "Kreyòl",
},
hu: {
nativeName: "Magyar",
},
"hu-HU": {
nativeName: "Magyar",
},
hy: {
nativeName: "Հայերեն",
},
"hy-AM": {
nativeName: "Հայերեն (Հայաստան)",
},
id: {
nativeName: "Bahasa Indonesia",
},
"id-ID": {
nativeName: "Bahasa Indonesia",
},
is: {
nativeName: "Íslenska",
},
"is-IS": {
nativeName: "Íslenska (Iceland)",
},
it: {
nativeName: "Italiano",
},
"it-IT": {
nativeName: "Italiano",
},
ja: {
nativeName: "日本語",
},
"ja-JP": {
nativeName: "日本語 (日本)",
},
"jv-ID": {
jv: {
nativeName: "Basa Jawa",
},
"ka-GE": {
ka: {
nativeName: "ქართული",
},
"kk-KZ": {
kk: {
nativeName: "Қазақша",
},
km: {
nativeName: "ភាសាខ្មែរ",
},
kl: {
nativeName: "kalaallisut",
},
"km-KH": {
km: {
nativeName: "ភាសាខ្មែរ",
},
kab: {
nativeName: "Taqbaylit",
},
kn: {
nativeName: "ಕನ್ನಡ",
},
"kn-IN": {
nativeName: "ಕನ್ನಡ (India)",
},
ko: {
nativeName: "한국어",
},
"ko-KR": {
nativeName: "한국어 (한국)",
},
"ku-TR": {
ku: {
nativeName: "Kurdî",
},
kw: {
@ -375,66 +162,39 @@ export const langmap = {
la: {
nativeName: "Latin",
},
"la-VA": {
nativeName: "Latin",
},
lb: {
nativeName: "Lëtzebuergesch",
},
"li-NL": {
li: {
nativeName: "Lèmbörgs",
},
lt: {
nativeName: "Lietuvių",
},
"lt-LT": {
nativeName: "Lietuvių",
},
lv: {
nativeName: "Latviešu",
},
"lv-LV": {
nativeName: "Latviešu",
},
mai: {
nativeName: "मैथिली, মৈথিলী",
},
"mg-MG": {
mg: {
nativeName: "Malagasy",
},
mk: {
nativeName: "Македонски",
},
"mk-MK": {
nativeName: "Македонски (Македонски)",
},
ml: {
nativeName: "മലയാളം",
},
"ml-IN": {
nativeName: "മലയാളം",
},
"mn-MN": {
mn: {
nativeName: "Монгол",
},
mr: {
nativeName: "मराठी",
},
"mr-IN": {
nativeName: "मराठी",
},
ms: {
nativeName: "Bahasa Melayu",
},
"ms-MY": {
nativeName: "Bahasa Melayu",
},
mt: {
nativeName: "Malti",
},
"mt-MT": {
nativeName: "Malti",
},
my: {
nativeName: "ဗမာစကာ",
},
@ -444,223 +204,179 @@ export const langmap = {
nb: {
nativeName: "Norsk (bokmål)",
},
"nb-NO": {
nativeName: "Norsk (bokmål)",
},
ne: {
nativeName: "नेपाली",
},
"ne-NP": {
nativeName: "नेपाली",
},
nl: {
nativeName: "Nederlands",
},
"nl-BE": {
nativeName: "Nederlands (België)",
},
"nl-NL": {
nativeName: "Nederlands (Nederland)",
},
"nn-NO": {
nn: {
nativeName: "Norsk (nynorsk)",
},
oc: {
nativeName: "Occitan",
},
"or-IN": {
or: {
nativeName: "ଓଡ଼ିଆ",
},
pa: {
nativeName: "ਪੰਜਾਬੀ",
},
"pa-IN": {
nativeName: "ਪੰਜਾਬੀ (ਭਾਰਤ ਨੂੰ)",
},
pl: {
nativeName: "Polski",
},
"pl-PL": {
nativeName: "Polski",
},
"ps-AF": {
ps: {
nativeName: "پښتو",
rtl: true,
},
pt: {
nativeName: "Português",
},
"pt-BR": {
nativeName: "Português (Brasil)",
},
"pt-PT": {
nativeName: "Português (Portugal)",
},
"qu-PE": {
qu: {
nativeName: "Qhichwa",
},
"rm-CH": {
rm: {
nativeName: "Rumantsch",
},
ro: {
nativeName: "Română",
},
"ro-RO": {
nativeName: "Română",
},
ru: {
nativeName: "Русский",
},
"ru-RU": {
nativeName: "Русский",
},
"sa-IN": {
sa: {
nativeName: "संस्कृतम्",
},
"se-NO": {
se: {
nativeName: "Davvisámegiella",
},
sh: {
nativeName: "српскохрватски",
},
"si-LK": {
si: {
nativeName: "සිංහල",
},
sk: {
nativeName: "Slovenčina",
},
"sk-SK": {
nativeName: "Slovenčina (Slovakia)",
},
sl: {
nativeName: "Slovenščina",
},
"sl-SI": {
nativeName: "Slovenščina",
},
"so-SO": {
so: {
nativeName: "Soomaaliga",
},
sq: {
nativeName: "Shqip",
},
"sq-AL": {
nativeName: "Shqip",
},
sr: {
nativeName: "Српски",
},
"sr-RS": {
nativeName: "Српски (Serbia)",
},
su: {
nativeName: "Basa Sunda",
},
sv: {
nativeName: "Svenska",
},
"sv-SE": {
nativeName: "Svenska",
},
sw: {
nativeName: "Kiswahili",
},
"sw-KE": {
nativeName: "Kiswahili",
},
ta: {
nativeName: "தமிழ்",
},
"ta-IN": {
nativeName: "தமிழ்",
},
te: {
nativeName: "తెలుగు",
},
"te-IN": {
nativeName: "తెలుగు",
},
tg: {
nativeName: "забо́ни тоҷикӣ́",
},
"tg-TJ": {
nativeName: "тоҷикӣ",
},
th: {
nativeName: "ภาษาไทย",
},
"th-TH": {
nativeName: "ภาษาไทย (ประเทศไทย)",
},
fil: {
nativeName: "Filipino",
},
tlh: {
nativeName: "tlhIngan-Hol",
},
tr: {
nativeName: "Türkçe",
},
"tr-TR": {
nativeName: "Türkçe",
},
"tt-RU": {
tt: {
nativeName: "татарча",
},
uk: {
nativeName: "Українська",
},
"uk-UA": {
nativeName: "Українська",
},
ur: {
nativeName: "اردو",
},
"ur-PK": {
nativeName: "اردو",
rtl: true,
},
uz: {
nativeName: "O'zbek",
},
"uz-UZ": {
nativeName: "O'zbek",
},
vi: {
nativeName: "Tiếng Việt",
},
"vi-VN": {
nativeName: "Tiếng Việt",
},
"xh-ZA": {
xh: {
nativeName: "isiXhosa",
},
yi: {
nativeName: "ייִדיש",
},
"yi-DE": {
nativeName: "ייִדיש (German)",
rtl: true,
},
zh: {
nativeName: "中文",
},
"zh-Hans": {
nativeName: "中文简体",
},
"zh-Hant": {
nativeName: "中文繁體",
},
"zh-CN": {
nativeName: "中文(中国大陆)",
},
"zh-HK": {
nativeName: "中文(香港)",
},
"zh-SG": {
nativeName: "中文(新加坡)",
},
"zh-TW": {
nativeName: "中文(台灣)",
},
"zu-ZA": {
zu: {
nativeName: "isiZulu",
},
};
export const iso639Langs3 = {
ach: {
nativeName: "Lwo",
},
ady: {
nativeName: "Адыгэбзэ",
},
cak: {
nativeName: "Maya Kaqchikel",
},
chr: {
nativeName: "ᏣᎳᎩ (tsalagi)",
},
dsb: {
nativeName: "Dolnoserbšćina",
},
fil: {
nativeName: "Filipino",
},
hsb: {
nativeName: "Hornjoserbšćina",
},
kab: {
nativeName: "Taqbaylit",
},
mai: {
nativeName: "मैथिली, মৈথিলী",
},
tlh: {
nativeName: "tlhIngan-Hol",
},
tok: {
nativeName: "Toki Pona",
},
yue: {
nativeName: "粵語",
},
nan: {
nativeName: "閩南語",
},
};
export const langmapNoRegion = Object.assign({}, iso639Langs1, iso639Langs3);
export const iso639Regional = {
"zh-hans": {
nativeName: "中文(简体)",
},
"zh-hant": {
nativeName: "中文(繁體)",
},
};
export const langmap = Object.assign({}, langmapNoRegion, iso639Regional);

View file

@ -369,6 +369,10 @@ export const defaultStore = markRaw(
| "ph" // this is ph-regular
| "ph-fill",
},
recentlyUsedPostLanguages: {
where: "account",
default: [] as string[],
},
useEmojiCdn: {
where: "device",
default: true,

View file

@ -58,3 +58,96 @@ export const permissions = [
"read:gallery-likes",
"write:gallery-likes",
];
export const languages = [
"ach",
"ady",
"af",
"ak",
"ar",
"az",
"bg",
"bn",
"br",
"ca",
"cak",
"cs",
"cy",
"da",
"de",
"dsb",
"el",
"en",
"eo",
"es",
"et",
"eu",
"fa",
"ff",
"fi",
"fo",
"fr",
"ga",
"gd",
"gl",
"gv",
"he",
"hi",
"hr",
"hsb",
"ht",
"hu",
"hy",
"id",
"is",
"it",
"ja",
"km",
"kl",
"kab",
"kn",
"ko",
"kw",
"la",
"lb",
"lt",
"lv",
"mai",
"mk",
"ml",
"mr",
"ms",
"mt",
"my",
"no",
"nb",
"ne",
"nl",
"oc",
"pa",
"pl",
"pt",
"ro",
"ru",
"sh",
"sk",
"sl",
"sq",
"sr",
"su",
"sv",
"sw",
"ta",
"te",
"tg",
"th",
"fil",
"tlh",
"tr",
"uk",
"ur",
"uz",
"vi",
"yi",
"zh",
] as const;

View file

@ -10,6 +10,7 @@ export const permissions = consts.permissions;
export const notificationTypes = consts.notificationTypes;
export const noteVisibilities = consts.noteVisibilities;
export const mutedNoteReasons = consts.mutedNoteReasons;
export const languages = consts.languages;
export const ffVisibility = consts.ffVisibility;
// api extractor not supported yet

View file

@ -399,9 +399,6 @@ importers:
tinycolor2:
specifier: 1.6.0
version: 1.6.0
tinyld:
specifier: ^1.3.4
version: 1.3.4
tmp:
specifier: 0.2.1
version: 0.2.1
@ -17158,6 +17155,7 @@ packages:
resolution: {integrity: sha512-u26CNoaInA4XpDU+8s/6Cq8xHc2T5M4fXB3ICfXPokUQoLzmPgSZU02TAkFwFMJCWTjk53gtkS8pETTreZwCqw==}
engines: {node: '>= 12.10.0', npm: '>= 6.12.0', yarn: '>= 1.20.0'}
hasBin: true
dev: true
/titleize@3.0.0:
resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}