Add post language selector and translate button by Namekuji, Increase CW limit to 256, More verbose instance is blocked errors by Jeder

This commit is contained in:
Crimekillz 2024-03-30 23:38:01 +01:00
parent 727fd4a3dc
commit 0588e3b85c
23 changed files with 635 additions and 819 deletions

View File

@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddLanguageSelector1706827327619 implements MigrationInterface {
async up(queryRunner: QueryRunner) {
await queryRunner.query(
`ALTER TABLE "note" ADD COLUMN IF NOT EXISTS "lang" character varying(10)`,
);
}
async down(queryRunner: QueryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN IF EXISTS "lang"`);
}
}

View File

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

@ -64,12 +64,18 @@ export class Note {
}) })
public threadId: string | null; public threadId: string | null;
@Index('note_text_fts_idx', { synchronize: false }) @Index("note_text_fts_idx", { synchronize: false })
@Column("text", { @Column("text", {
nullable: true, nullable: true,
}) })
public text: string | null; public text: string | null;
@Column("varchar", {
length: 10,
nullable: true,
})
public lang: string | null;
@Column("varchar", { @Column("varchar", {
length: 256, length: 256,
nullable: true, nullable: true,
@ -123,7 +129,7 @@ export class Note {
* specified ... visibleUserIds * specified ... visibleUserIds
*/ */
@Column("enum", { enum: noteVisibilities }) @Column("enum", { enum: noteVisibilities })
public visibility: typeof noteVisibilities[number]; public visibility: (typeof noteVisibilities)[number];
@Index({ unique: true }) @Index({ unique: true })
@Column("varchar", { @Column("varchar", {

View File

@ -228,6 +228,7 @@ export const NoteRepository = db.getRepository(Note).extend({
detail: false, detail: false,
}), }),
text: text, text: text,
lang: note.lang,
cw: note.cw, cw: note.cw,
visibility: note.visibility, visibility: note.visibility,
localOnly: note.localOnly || undefined, localOnly: note.localOnly || undefined,
@ -262,7 +263,6 @@ export const NoteRepository = db.getRepository(Note).extend({
isFiltered: isFiltered(note, me), isFiltered: isFiltered(note, me),
} }
: {}), : {}),
...(opts.detail ...(opts.detail
? { ? {
reply: note.replyId reply: note.replyId

View File

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

View File

@ -46,7 +46,10 @@ import { extractApMentions } from "./mention.js";
import DbResolver from "../db-resolver.js"; import DbResolver from "../db-resolver.js";
import { StatusError } from "@/misc/fetch.js"; import { StatusError } from "@/misc/fetch.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js"; import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import { publishNoteStream, publishNoteUpdatesStream } from "@/services/stream.js"; import {
publishNoteStream,
publishNoteUpdatesStream,
} from "@/services/stream.js";
import { extractHashtags } from "@/misc/extract-hashtags.js"; import { extractHashtags } from "@/misc/extract-hashtags.js";
import { UserProfiles } from "@/models/index.js"; import { UserProfiles } from "@/models/index.js";
import { In } from "typeorm"; import { In } from "typeorm";
@ -55,6 +58,7 @@ import { truncate } from "@/misc/truncate.js";
import { type Size, getEmojiSize } from "@/misc/emoji-meta.js"; import { type Size, getEmojiSize } from "@/misc/emoji-meta.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { RecursionLimiter } from "@/models/repositories/user-profile.js"; import { RecursionLimiter } from "@/models/repositories/user-profile.js";
import { langmap } from "@/misc/langmap.js";
const logger = apLogger; const logger = apLogger;
@ -110,7 +114,7 @@ export async function createNote(
value: string | IObject, value: string | IObject,
resolver?: Resolver, resolver?: Resolver,
silent = false, silent = false,
limiter: RecursionLimiter = new RecursionLimiter() limiter: RecursionLimiter = new RecursionLimiter(),
): Promise<Note | null> { ): Promise<Note | null> {
if (resolver == null) resolver = new Resolver(); if (resolver == null) resolver = new Resolver();
@ -166,7 +170,7 @@ export async function createNote(
const actor = (await resolvePerson( const actor = (await resolvePerson(
getOneApId(note.attributedTo), getOneApId(note.attributedTo),
resolver, resolver,
limiter limiter,
)) as CacheableRemoteUser; )) as CacheableRemoteUser;
// Skip if author is suspended. // Skip if author is suspended.
@ -177,7 +181,13 @@ export async function createNote(
return null; return null;
} }
const noteAudience = await parseAudience(actor, note.to, note.cc, undefined, limiter); const noteAudience = await parseAudience(
actor,
note.to,
note.cc,
undefined,
limiter,
);
let visibility = noteAudience.visibility; let visibility = noteAudience.visibility;
const visibleUsers = noteAudience.visibleUsers; const visibleUsers = noteAudience.visibleUsers;
@ -309,11 +319,25 @@ export async function createNote(
// Text parsing // Text parsing
let text: string | null = null; let text: string | null = null;
let lang: string | null = null;
if ( if (
note.source?.mediaType === "text/x.misskeymarkdown" && note.source?.mediaType === "text/x.misskeymarkdown" &&
typeof note.source?.content === "string" typeof note.source?.content === "string"
) { ) {
text = note.source.content; text = note.source.content;
if (note.contentMap) {
const key = Object.keys(note.contentMap).shift()?.toLowerCase();
if (key) {
lang = Object.keys(langmap).includes(key) ? key : null;
}
}
} else if (note.contentMap) {
const entry = Object.entries(note.contentMap).shift();
if (entry) {
const key = entry[0].toLowerCase();
lang = Object.keys(langmap).includes(key) ? key : null;
text = await htmlToMfm(entry[1], note.tag);
}
} else if (typeof note._misskey_content !== "undefined") { } else if (typeof note._misskey_content !== "undefined") {
text = note._misskey_content; text = note._misskey_content;
} else if (typeof note.content === "string") { } else if (typeof note.content === "string") {
@ -384,6 +408,7 @@ export async function createNote(
name: note.name, name: note.name,
cw, cw,
text, text,
lang,
localOnly: false, localOnly: false,
visibility, visibility,
visibleUsers, visibleUsers,
@ -395,7 +420,7 @@ export async function createNote(
url: url, url: url,
}, },
silent, silent,
limiter limiter,
); );
} }
@ -408,7 +433,7 @@ export async function createNote(
export async function resolveNote( export async function resolveNote(
value: string | IObject, value: string | IObject,
resolver?: Resolver, resolver?: Resolver,
limiter: RecursionLimiter = new RecursionLimiter() limiter: RecursionLimiter = new RecursionLimiter(),
): Promise<Note | null> { ): Promise<Note | null> {
const uri = typeof value === "string" ? value : value.id; const uri = typeof value === "string" ? value : value.id;
if (uri == null) throw new Error("missing uri"); if (uri == null) throw new Error("missing uri");
@ -573,11 +598,25 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
// Text parsing // Text parsing
let text: string | null = null; let text: string | null = null;
let lang: string | null = null;
if ( if (
post.source?.mediaType === "text/x.misskeymarkdown" && post.source?.mediaType === "text/x.misskeymarkdown" &&
typeof post.source?.content === "string" typeof post.source?.content === "string"
) { ) {
text = post.source.content; text = post.source.content;
if (post.contentMap) {
const key = Object.keys(post.contentMap).shift()?.toLowerCase();
if (key) {
lang = Object.keys(langmap).includes(key) ? key : null;
}
}
} else if (post.contentMap) {
const entry = Object.entries(post.contentMap).shift();
if (entry) {
const key = entry[0].toLowerCase();
lang = Object.keys(langmap).includes(key) ? key : null;
text = await htmlToMfm(entry[1], post.tag);
}
} else if (typeof post._misskey_content !== "undefined") { } else if (typeof post._misskey_content !== "undefined") {
text = post._misskey_content; text = post._misskey_content;
} else if (typeof post.content === "string") { } else if (typeof post.content === "string") {
@ -672,6 +711,9 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
if (text && text !== note.text) { if (text && text !== note.text) {
update.text = text; update.text = text;
} }
if (lang && lang !== note.lang) {
update.lang = lang
}
if (cw !== note.cw) { if (cw !== note.cw) {
update.cw = cw ? cw : null; update.cw = cw ? cw : null;
} }
@ -712,7 +754,8 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
}); });
updating = true; updating = true;
} else { } else {
const choicesChanged = JSON.stringify(dbPoll.choices) !== JSON.stringify(poll.choices); const choicesChanged =
JSON.stringify(dbPoll.choices) !== JSON.stringify(poll.choices);
if ( if (
dbPoll.multiple !== poll.multiple || dbPoll.multiple !== poll.multiple ||
@ -778,7 +821,7 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
const updatedNote = { const updatedNote = {
...note, ...note,
...update ...update,
}; };
publishNoteUpdatesStream("updated", updatedNote); publishNoteUpdatesStream("updated", updatedNote);

View File

@ -10,6 +10,7 @@ import renderEmoji from "./emoji.js";
import renderMention from "./mention.js"; import renderMention from "./mention.js";
import renderHashtag from "./hashtag.js"; import renderHashtag from "./hashtag.js";
import renderDocument from "./document.js"; import renderDocument from "./document.js";
import type { IPost } from "@/remote/activitypub/type.js";
export default async function renderNote( export default async function renderNote(
note: Note, note: Note,
@ -114,6 +115,13 @@ export default async function renderNote(
}), }),
); );
let contentMap: IPost["contentMap"] = undefined;
if (note.lang) {
contentMap = {
[note.lang]: content ?? ""
}
}
const emojis = await getEmojis(note.emojis); const emojis = await getEmojis(note.emojis);
const apemojis = emojis.map((emoji) => renderEmoji(emoji)); const apemojis = emojis.map((emoji) => renderEmoji(emoji));
@ -152,6 +160,7 @@ export default async function renderNote(
attributedTo, attributedTo,
summary, summary,
content, content,
contentMap,
_misskey_content: text, _misskey_content: text,
source: { source: {
content: text, content: text,

View File

@ -72,7 +72,7 @@ export default class Resolver {
if (typeof value.id !== "undefined") { if (typeof value.id !== "undefined") {
const host = extractDbHost(getApId(value)); const host = extractDbHost(getApId(value));
if (await shouldBlockInstance(host)) { if (await shouldBlockInstance(host)) {
throw new Error("instance is blocked"); throw new Error(`instance ${host} is blocked`);
} }
} }
apLogger.debug("Returning existing object:"); apLogger.debug("Returning existing object:");
@ -104,7 +104,7 @@ export default class Resolver {
const meta = await fetchMeta(); const meta = await fetchMeta();
if (await shouldBlockInstance(host, meta)) { if (await shouldBlockInstance(host, meta)) {
throw new Error("Instance is blocked"); throw new Error(`Instance ${host} is blocked`);
} }
if ( if (
@ -113,7 +113,7 @@ export default class Resolver {
config.domain !== host && config.domain !== host &&
!meta.allowedHosts.includes(host) !meta.allowedHosts.includes(host)
) { ) {
throw new Error("Instance is not allowed"); throw new Error(`Instance ${host} is not allowed`);
} }
if (!this.user) { if (!this.user) {

View File

@ -134,6 +134,7 @@ export interface IPost extends IObject {
content: string; content: string;
mediaType: string; mediaType: string;
}; };
contentMap?: Record<string, string>,
_misskey_quote?: string; _misskey_quote?: string;
quoteUrl?: string; quoteUrl?: string;
quoteUri?: string; quoteUri?: string;

View File

@ -91,7 +91,7 @@ export const paramDef = {
birthday: { ...Users.birthdaySchema, nullable: true }, birthday: { ...Users.birthdaySchema, nullable: true },
lang: { lang: {
type: "string", type: "string",
enum: [null, ...Object.keys(langmap)], enum: Object.keys(langmap),
nullable: true, nullable: true,
}, },
avatarId: { type: "string", format: "misskey:id", nullable: true }, avatarId: { type: "string", format: "misskey:id", nullable: true },
@ -159,7 +159,7 @@ export default define(meta, paramDef, async (ps, _user, token) => {
if (ps.name !== undefined) updates.name = ps.name; if (ps.name !== undefined) updates.name = ps.name;
if (ps.description !== undefined) profileUpdates.description = ps.description; 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.location !== undefined) profileUpdates.location = ps.location;
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday; if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
if (ps.ffVisibility !== undefined) if (ps.ffVisibility !== undefined)

View File

@ -17,6 +17,7 @@ import { ApiError } from "../../error.js";
import define from "../../define.js"; import define from "../../define.js";
import { HOUR } from "@/const.js"; import { HOUR } from "@/const.js";
import { getNote } from "../../common/getters.js"; import { getNote } from "../../common/getters.js";
import { langmap } from "@/misc/langmap.js";
export const meta = { export const meta = {
tags: ["notes"], tags: ["notes"],
@ -108,7 +109,12 @@ export const paramDef = {
}, },
}, },
text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true }, text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true },
cw: { type: "string", nullable: true, maxLength: 100 }, lang: {
type: "string",
enum: Object.keys(langmap),
nullable: true,
},
cw: { type: "string", nullable: true, maxLength: 256 },
localOnly: { type: "boolean", default: false }, localOnly: { type: "boolean", default: false },
noExtractMentions: { type: "boolean", default: false }, noExtractMentions: { type: "boolean", default: false },
noExtractHashtags: { type: "boolean", default: false }, noExtractHashtags: { type: "boolean", default: false },
@ -294,6 +300,7 @@ export default define(meta, paramDef, async (ps, user) => {
} }
: undefined, : undefined,
text: ps.text || undefined, text: ps.text || undefined,
lang: ps.lang,
reply, reply,
renote, renote,
cw: ps.cw, cw: ps.cw,

View File

@ -6,8 +6,9 @@ import { ApiError } from "../../error.js";
import define from "../../define.js"; import define from "../../define.js";
import { HOUR } from "@/const.js"; import { HOUR } from "@/const.js";
// import { deliverQuestionUpdate } from "@/services/note/polls/update.js"; // import { deliverQuestionUpdate } from "@/services/note/polls/update.js";
import editNote from "@/services/note/edit.js" import editNote from "@/services/note/edit.js";
import { Packed } from "@/misc/schema.js"; import { Packed } from "@/misc/schema.js";
import { langmap } from "@/misc/langmap.js";
export const meta = { export const meta = {
tags: ["notes"], tags: ["notes"],
@ -25,7 +26,7 @@ export const meta = {
type: "object", type: "object",
optional: false, optional: false,
nullable: false, nullable: false,
ref: "Note" ref: "Note",
}, },
errors: { errors: {
@ -64,7 +65,7 @@ export const meta = {
code: "NOT_LOCAL_USER", code: "NOT_LOCAL_USER",
id: "b907f407-2aa0-4283-800b-a2c56290b822", id: "b907f407-2aa0-4283-800b-a2c56290b822",
}, },
} },
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -72,6 +73,11 @@ export const paramDef = {
properties: { properties: {
editId: { type: "string", format: "misskey:id" }, editId: { type: "string", format: "misskey:id" },
text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true }, text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true },
lang: {
type: "string",
enum: Object.keys(langmap),
nullable: true,
},
cw: { type: "string", nullable: true, maxLength: 250 }, cw: { type: "string", nullable: true, maxLength: 250 },
fileIds: { fileIds: {
type: "array", type: "array",
@ -125,7 +131,10 @@ export const paramDef = {
], ],
} as const; } as const;
export default define(meta, paramDef, async (ps, user): Promise<Packed<"Note">> => { export default define(
meta,
paramDef,
async (ps, user): Promise<Packed<"Note">> => {
if (user.movedToUri != null) throw new ApiError(meta.errors.accountLocked); if (user.movedToUri != null) throw new ApiError(meta.errors.accountLocked);
if (!Users.isLocalUser(user)) { if (!Users.isLocalUser(user)) {
@ -148,7 +157,10 @@ export default define(meta, paramDef, async (ps, user): Promise<Packed<"Note">>
throw new ApiError(meta.errors.youAreNotTheAuthor); throw new ApiError(meta.errors.youAreNotTheAuthor);
} }
if (ps.poll?.expiresAt && new Date(ps.poll.expiresAt).getTime() < new Date().getTime()) { if (
ps.poll?.expiresAt &&
new Date(ps.poll.expiresAt).getTime() < new Date().getTime()
) {
throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll);
} }
@ -167,6 +179,7 @@ export default define(meta, paramDef, async (ps, user): Promise<Packed<"Note">>
note = await editNote(user, note, { note = await editNote(user, note, {
text: ps.text, text: ps.text,
lang: ps.lang,
cw: ps.cw, cw: ps.cw,
poll: ps.poll poll: ps.poll
? { ? {
@ -175,8 +188,9 @@ export default define(meta, paramDef, async (ps, user): Promise<Packed<"Note">>
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
} }
: undefined, : undefined,
files: files files: files,
}); });
return Notes.pack(note, user); return Notes.pack(note, user);
}); },
);

View File

@ -62,6 +62,7 @@ import { redisClient } from "@/db/redis.js";
import { Mutex } from "redis-semaphore"; import { Mutex } from "redis-semaphore";
import { RecursionLimiter } from "@/models/repositories/user-profile.js"; import { RecursionLimiter } from "@/models/repositories/user-profile.js";
import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
import { langmap } from "@/misc/langmap.js";
type NotificationType = "reply" | "renote" | "quote" | "mention"; type NotificationType = "reply" | "renote" | "quote" | "mention";
@ -130,6 +131,7 @@ type Option = {
createdAt?: Date | null; createdAt?: Date | null;
name?: string | null; name?: string | null;
text?: string | null; text?: string | null;
lang?: string | null;
reply?: Note | null; reply?: Note | null;
renote?: Note | null; renote?: Note | null;
files?: DriveFile[] | null; files?: DriveFile[] | null;
@ -160,7 +162,7 @@ export default async (
silent = false, silent = false,
limiter: RecursionLimiter = new RecursionLimiter() limiter: RecursionLimiter = new RecursionLimiter()
) => ) =>
// rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME // biome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
new Promise<Note>(async (res, rej) => { new Promise<Note>(async (res, rej) => {
const dontFederateInitially = data.visibility === "hidden"; const dontFederateInitially = data.visibility === "hidden";
@ -265,6 +267,15 @@ export default async (
data.text = null; data.text = null;
} }
if (data.lang) {
if (!Object.keys(langmap).includes(data.lang.toLowerCase())) {
throw new Error("invalid lang param");
}
data.lang = data.lang.toLowerCase();
} else {
data.lang = null;
}
let tags = data.apHashtags; let tags = data.apHashtags;
let emojis = data.apEmojis; let emojis = data.apEmojis;
let mentionedUsers = data.apMentions; let mentionedUsers = data.apMentions;
@ -670,6 +681,7 @@ async function insertNote(
: null, : null,
name: data.name, name: data.name,
text: data.text, text: data.text,
lang: data.lang,
hasPoll: data.poll != null, hasPoll: data.poll != null,
cw: data.cw == null ? null : data.cw, cw: data.cw == null ? null : data.cw,
tags: tags.map((tag) => normalizeForSearch(tag)), tags: tags.map((tag) => normalizeForSearch(tag)),

View File

@ -27,9 +27,11 @@ import renderUpdate from "@/remote/activitypub/renderer/update.js";
import { extractMentionedUsers } from "@/services/note/create.js"; import { extractMentionedUsers } from "@/services/note/create.js";
import { normalizeForSearch } from "@/misc/normalize-for-search.js"; import { normalizeForSearch } from "@/misc/normalize-for-search.js";
import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
import { langmap } from "@/misc/langmap.js";
type Option = { type Option = {
text?: string | null; text?: string | null;
lang?: string | null;
files?: DriveFile[] | null; files?: DriveFile[] | null;
poll?: IPoll | null; poll?: IPoll | null;
cw?: string | null; cw?: string | null;
@ -81,10 +83,18 @@ export default async function (
}); });
let publishing = false; let publishing = false;
const update = {} as Partial<Note>; const update: Partial<Note> = {};
if (data.text !== null && data.text !== note.text) { if (data.text !== null && data.text !== note.text) {
update.text = data.text; update.text = data.text;
} }
if (data.lang) {
const langLowerCase = data.lang.toLowerCase();
if (!Object.keys(langmap).includes(langLowerCase))
throw new Error("invalid param");
update.lang = langLowerCase;
} else {
update.lang = null;
}
if (data.cw !== note.cw) { if (data.cw !== note.cw) {
update.cw = data.cw ?? null; update.cw = data.cw ?? null;
} }
@ -239,4 +249,3 @@ export default async function (
function notEmpty(partial: Partial<any>) { function notEmpty(partial: Partial<any>) {
return Object.keys(partial).length > 0; return Object.keys(partial).length > 0;
} }

View File

@ -221,6 +221,18 @@
<i class="ph-minus ph-bold ph-lg"></i> <i class="ph-minus ph-bold ph-lg"></i>
</button> </button>
<XQuoteButton class="button" :note="appearNote" /> <XQuoteButton class="button" :note="appearNote" />
<button
v-if="
$i &&
isForeignLanguage &&
!translation
"
v-tooltip.noDelay.bottom="i18n.ts.translate"
class="button _button"
@click.stop="translate"
>
<i class="ph-translate ph-bold ph-lg"></i>
</button>
<button <button
ref="menuButton" ref="menuButton"
v-tooltip.noDelay.bottom="i18n.ts.more" v-tooltip.noDelay.bottom="i18n.ts.more"
@ -355,6 +367,45 @@ const translating = ref(false);
const enableEmojiReactions = defaultStore.state.enableEmojiReactions; const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
const expandOnNoteClick = defaultStore.state.expandOnNoteClick; const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
const lang = localStorage.getItem("lang");
const isForeignLanguage: boolean =
appearNote.text &&
(() => {
const targetLang = (lang || navigator.language)?.slice(
0,
2,
);
const postLang = appearNote.lang;
return postLang !== "" && postLang !== targetLang;
})();
async function translate_(noteId: string, targetLang: string) {
return await os.api("notes/translate", {
noteId,
targetLang,
});
}
async function translate() {
if (translation.value) return;
translating.value = true;
translation.value = await translate_(
appearNote.id,
lang || navigator.language,
);
if (
lang &&
(!translation.value ||
translation.value.sourceLang.toLowerCase() ===
lang.slice(0, 2))
) {
translation.value = await translate_(appearNote.id, lang);
}
translating.value = false;
}
const keymap = { const keymap = {
r: () => reply(true), r: () => reply(true),
"e|a|plus": () => react(true), "e|a|plus": () => react(true),

View File

@ -51,6 +51,20 @@
><i class="ph-envelope-simple-open ph-bold ph-lg"></i ><i class="ph-envelope-simple-open ph-bold ph-lg"></i
></span> ></span>
</button> </button>
<button
ref="languageButton"
v-tooltip="i18n.ts.language"
class="_button language"
@click="setLanguage"
>
<i
v-if="language === '' || language == null"
class="_button ph-seal-warning ph-bold ph-lg"
></i>
<p v-else class="_button" style="font-weight: bold">
{{ language.split("-")[0] }}
</p>
</button>
<button <button
v-tooltip="i18n.ts.previewNoteText" v-tooltip="i18n.ts.previewNoteText"
class="_button preview" class="_button preview"
@ -268,6 +282,8 @@ import { uploadFile } from "@/scripts/upload";
import { deepClone } from "@/scripts/clone"; import { deepClone } from "@/scripts/clone";
import XCheatSheet from "@/components/MkCheatSheetDialog.vue"; import XCheatSheet from "@/components/MkCheatSheetDialog.vue";
import { preprocess } from "@/scripts/preprocess"; import { preprocess } from "@/scripts/preprocess";
import { langmap } from "@/scripts/langmap";
import { MenuItem } from "@/types/menu";
const modal = inject("modal"); const modal = inject("modal");
@ -280,6 +296,7 @@ const props = withDefaults(
specified?: misskey.entities.User; specified?: misskey.entities.User;
initialText?: string; initialText?: string;
initialVisibility?: typeof misskey.noteVisibilities; initialVisibility?: typeof misskey.noteVisibilities;
initialLanguage?: typeof misskey.languages;
initialFiles?: misskey.entities.DriveFile[]; initialFiles?: misskey.entities.DriveFile[];
initialLocalOnly?: boolean; initialLocalOnly?: boolean;
initialVisibleUsers?: misskey.entities.User[]; initialVisibleUsers?: misskey.entities.User[];
@ -307,6 +324,7 @@ const textareaEl = $ref<HTMLTextAreaElement | null>(null);
const cwInputEl = $ref<HTMLInputElement | null>(null); const cwInputEl = $ref<HTMLInputElement | null>(null);
const hashtagsInputEl = $ref<HTMLInputElement | null>(null); const hashtagsInputEl = $ref<HTMLInputElement | null>(null);
const visibilityButton = $ref<HTMLElement | null>(null); const visibilityButton = $ref<HTMLElement | null>(null);
const languageButton = $ref<HTMLElement | null>(null);
let posting = $ref(false); let posting = $ref(false);
let text = $ref(props.initialText ?? ""); let text = $ref(props.initialText ?? "");
@ -342,6 +360,11 @@ let quoteId = $ref(null);
let hasNotSpecifiedMentions = $ref(false); let hasNotSpecifiedMentions = $ref(false);
let recentHashtags = $ref(JSON.parse(localStorage.getItem("hashtags") || "[]")); let recentHashtags = $ref(JSON.parse(localStorage.getItem("hashtags") || "[]"));
let imeText = $ref(""); let imeText = $ref("");
let language = $ref<string | null>(
props.initialLanguage ??
defaultStore.state.recentlyUsedPostLanguages[0] ??
localStorage.getItem("lang")?.split("-")[0],
);
const typing = throttle(3000, () => { const typing = throttle(3000, () => {
if (props.channel) { if (props.channel) {
@ -534,6 +557,7 @@ function watchForDraft() {
watch($$(files), () => saveDraft(), { deep: true }); watch($$(files), () => saveDraft(), { deep: true });
watch($$(visibility), () => saveDraft()); watch($$(visibility), () => saveDraft());
watch($$(localOnly), () => saveDraft()); watch($$(localOnly), () => saveDraft());
watch($$(language), () => saveDraft());
} }
function checkMissingMention() { function checkMissingMention() {
@ -664,6 +688,65 @@ function setVisibility() {
); );
} }
function setLanguage() {
const actions: Array<MenuItem> = [];
if (language) {
actions.push({
text: langmap[language].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) continue;
if (!langs.includes(lang)) continue;
actions.push({
text: langmap[lang].nativeName,
danger: false,
active: false,
action: () => {
language = lang;
},
});
recentlyUsedLanguagesExist = true;
}
if (recentlyUsedLanguagesExist) actions.push(null);
actions.push({
text: "N/A",
danger: false,
active: false,
action: () => {
language = null;
},
});
for (const lang of langs) {
if (lang === language) continue;
if (defaultStore.state.recentlyUsedPostLanguages.includes(lang))
continue;
actions.push({
text: langmap[lang].nativeName,
danger: false,
active: false,
action: () => {
language = lang;
},
});
}
if (languageButton) {
os.popupMenu(actions, languageButton, {});
}
}
function pushVisibleUser(user) { function pushVisibleUser(user) {
if ( if (
!visibleUsers.some( !visibleUsers.some(
@ -807,6 +890,7 @@ function saveDraft() {
updatedAt: new Date(), updatedAt: new Date(),
data: { data: {
text: text, text: text,
lang: language,
useCw: useCw, useCw: useCw,
cw: cw, cw: cw,
visibility: visibility, visibility: visibility,
@ -840,6 +924,7 @@ async function post() {
} }
: { : {
text: processedText === "" ? undefined : processedText, text: processedText === "" ? undefined : processedText,
lang: language ?? undefined,
fileIds: files.length > 0 ? files.map((f) => f.id) : undefined, fileIds: files.length > 0 ? files.map((f) => f.id) : undefined,
replyId: props.reply ? props.reply.id : undefined, replyId: props.reply ? props.reply.id : undefined,
renoteId: props.renote renoteId: props.renote
@ -942,6 +1027,27 @@ async function post() {
text: err.message + "\n" + (err as any).id, text: err.message + "\n" + (err as any).id,
}); });
}); });
if (language) {
const languages = Object.keys(langmap);
const maxLength = 6;
defaultStore.set(
"recentlyUsedPostLanguages",
[language]
.concat(
defaultStore.state.recentlyUsedPostLanguages.filter(
(lang) => {
return (
lang !== language &&
languages.includes(lang)
);
},
),
)
.slice(0, maxLength),
);
}
} }
function cancel() { function cancel() {
@ -1018,13 +1124,14 @@ onMounted(() => {
new Autocomplete(hashtagsInputEl, $$(hashtags)); new Autocomplete(hashtagsInputEl, $$(hashtags));
nextTick(() => { nextTick(() => {
// 稿 // Restore the draft post
if (!props.instant && !props.mention && !props.specified) { if (!props.instant && !props.mention && !props.specified) {
const draft = JSON.parse(localStorage.getItem("drafts") || "{}")[ const draft = JSON.parse(localStorage.getItem("drafts") || "{}")[
draftKey draftKey
]; ];
if (draft) { if (draft) {
text = draft.data.text; text = draft.data.text;
language = draft.data.lang;
useCw = draft.data.useCw; useCw = draft.data.useCw;
cw = draft.data.cw; cw = draft.data.cw;
visibility = draft.data.visibility; visibility = draft.data.visibility;
@ -1038,10 +1145,11 @@ onMounted(() => {
} }
} }
// // Delete then edit
if (props.initialNote) { if (props.initialNote) {
const init = props.initialNote; const init = props.initialNote;
text = init.text ? init.text : ""; text = init.text ? init.text : "";
language = init.lang;
files = init.files; files = init.files;
cw = init.cw; cw = init.cw;
useCw = init.cw != null; useCw = init.cw != null;
@ -1132,6 +1240,11 @@ onMounted(() => {
opacity: 0.7; opacity: 0.7;
} }
> .language {
height: 34px;
width: 34px;
}
> .preview { > .preview {
display: inline-block; display: inline-block;
padding: 0; padding: 0;

View File

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

View File

@ -118,7 +118,7 @@ function checkForSplash() {
//#region Set lang attr //#region Set lang attr
const html = document.documentElement; const html = document.documentElement;
html.setAttribute("lang", lang || "en-US"); html.setAttribute("lang", lang ?? "en-US");
//#endregion //#endregion
//#region loginId //#region loginId

View File

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

@ -107,6 +107,10 @@ export const defaultStore = markRaw(
where: "account", where: "account",
default: [], default: [],
}, },
recentlyUsedPostLanguages: {
where: "account",
default: [] as string[],
},
menu: { menu: {
where: "deviceAccount", where: "deviceAccount",
default: menuOptions, default: menuOptions,

View File

@ -57,3 +57,96 @@ export const permissions = [
"read:gallery-likes", "read:gallery-likes",
"write: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

@ -137,6 +137,7 @@ export type Note = {
id: ID; id: ID;
createdAt: DateString; createdAt: DateString;
text: string | null; text: string | null;
lang?: string | null;
cw: string | null; cw: string | null;
user: User; user: User;
userId: User["id"]; userId: User["id"];

View File

@ -9,6 +9,7 @@ export { Endpoints, Stream, Connection as ChannelConnection, Channels, Acct };
export const permissions = consts.permissions; export const permissions = consts.permissions;
export const notificationTypes = consts.notificationTypes; export const notificationTypes = consts.notificationTypes;
export const noteVisibilities = consts.noteVisibilities; export const noteVisibilities = consts.noteVisibilities;
export const languages = consts.languages;
export const ffVisibility = consts.ffVisibility; export const ffVisibility = consts.ffVisibility;
// api extractor not supported yet // api extractor not supported yet