WIP: Post languages #63

Draft
leah wants to merge 2 commits from feature/post-languages into main
23 changed files with 1619 additions and 25 deletions

View file

@ -0,0 +1,11 @@
export class AddLanguageSelector1706827327619 {
async up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "note" ADD "lang" character varying(10)`,
);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "lang"`);
}
}

View file

@ -72,6 +72,7 @@ import { trackPromise } from '@/misc/promise-tracker.js';
import { isNotNull } from '@/misc/is-not-null.js'; import { isNotNull } from '@/misc/is-not-null.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { CollapsedQueue } from '@/misc/collapsed-queue.js'; import { CollapsedQueue } from '@/misc/collapsed-queue.js';
import { SUPPORTED_POST_LOCALES } from "@/misc/langmap.js";
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -342,6 +343,15 @@ export class NoteCreateService implements OnApplicationShutdown {
data.text = null; data.text = null;
} }
if (data.lang) {
if (!Object.keys(SUPPORTED_POST_LOCALES).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;
@ -458,6 +468,7 @@ export class NoteCreateService implements OnApplicationShutdown {
: 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, cw: data.cw ?? null,
tags: tags.map(tag => normalizeForSearch(tag)), tags: tags.map(tag => normalizeForSearch(tag)),

View file

@ -39,6 +39,7 @@ import { ApQuestionService } from './ApQuestionService.js';
import { ApImageService } from './ApImageService.js'; import { ApImageService } from './ApImageService.js';
import type { Resolver } from '../ApResolverService.js'; import type { Resolver } from '../ApResolverService.js';
import type { IObject, IPost } from '../type.js'; import type { IObject, IPost } from '../type.js';
import {Obj} from "@/misc/json-schema.js";
@Injectable() @Injectable()
export class ApNoteService { export class ApNoteService {
@ -204,12 +205,17 @@ export class ApNoteService {
// テキストのパース // テキストのパース
let text: string | null = null; let text: string | null = null;
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') { let lang: string | null = null;
text = note.source.content; if (note.source?.mediaType === 'text/x.misskeymarkdown' && (typeof note.source.content === 'string' || note.source.contentMap)) {
const guessed = this.guessLang(note.source);
text = guessed.text;
lang = guessed.lang;
} 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' || note.contentMap) {
text = this.apMfmService.htmlToMfm(note.content, note.tag); const guessed = this.guessLang(note);
lang = guessed.lang;
if (guessed.text) text = this.apMfmService.htmlToMfm(guessed.text, note.tag);
} }
const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
@ -343,6 +349,7 @@ export class ApNoteService {
name: note.name, name: note.name,
cw, cw,
text, text,
lang,
localOnly: false, localOnly: false,
visibility, visibility,
visibleUsers, visibleUsers,
@ -460,4 +467,40 @@ export class ApNoteService {
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); }).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
})); }));
} }
@bindThis
private guessLang(source: { contentMap?: Record<string, string | null>, content?: string | null }): { lang: string | null, text: string | null } {
// do we have a map?
if (source.contentMap) {
const entries = Object.entries(source.contentMap);
// only one entry: take that
if (entries.length === 1) {
return { lang: entries[0][0], text: entries[0][1] };
}
// did the sender indicate a preferred language?
if (source.content) {
for (const e of entries) {
if (e[1] === source.content) {
return { lang: e[0], text: e[1] };
}
}
}
// can we find one of *our* preferred languages?
// for (const prefLang of this.config.langPref) {
// if (source.contentMap[prefLang]) {
// return { lang: prefLang, text: source.contentMap[prefLang] };
// }
// }
// bah, just pick one
return { lang: entries[0][0], text: entries[0][1] };
}
// no map, so we don't know the language, just take whatever
// content we got
return { lang: null, text: source.content ?? null };
}
} }

View file

@ -11,6 +11,7 @@ export interface IObject {
type: string | string[]; type: string | string[];
id?: string; id?: string;
name?: string | null; name?: string | null;
contentMap?: Record<string, string | null>;
summary?: string; summary?: string;
_misskey_summary?: string; _misskey_summary?: string;
published?: string; published?: string;
@ -122,6 +123,7 @@ export interface IPost extends IObject {
type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event'; type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event';
source?: { source?: {
content: string; content: string;
contentMap?: Record<string, string | null>;
mediaType: string; mediaType: string;
}; };
_misskey_quote?: string; _misskey_quote?: string;
@ -134,6 +136,7 @@ export interface IQuestion extends IObject {
actor: string; actor: string;
source?: { source?: {
content: string; content: string;
contentMap?: Record<string, string | null>;
mediaType: string; mediaType: string;
}; };
_misskey_quote?: string; _misskey_quote?: string;

View file

@ -331,6 +331,7 @@ export class NoteEntityService implements OnModuleInit {
userId: note.userId, userId: note.userId,
user: this.userEntityService.pack(note.user ?? note.userId, me), user: this.userEntityService.pack(note.user ?? note.userId, me),
text: text, text: text,
lang: note.lang,
cw: note.cw, cw: note.cw,
visibility: note.visibility, visibility: note.visibility,
localOnly: note.localOnly, localOnly: note.localOnly,

View file

@ -669,3 +669,631 @@ export const langmap = {
nativeName: 'isiZulu', nativeName: 'isiZulu',
}, },
}; };
export const ISO_639_1 = {
aa: {
nativeName: 'Afaraf',
},
ab: {
nativeName: 'аҧсуа бызшәа',
},
ae: {
nativeName: 'avesta',
},
af: {
nativeName: 'Afrikaans',
},
ak: {
nativeName: 'Akan',
},
am: {
nativeName: 'አማርኛ',
},
an: {
nativeName: 'aragonés',
},
ar: {
nativeName: 'اللغة العربية',
},
as: {
nativeName: 'অসমীয়া',
},
av: {
nativeName: 'авар мацӀ',
},
ay: {
nativeName: 'aymar aru',
},
az: {
nativeName: 'azərbaycan dili',
},
ba: {
nativeName: 'башҡорт теле',
},
be: {
nativeName: 'беларуская мова',
},
bg: {
nativeName: 'български език',
},
bh: {
nativeName: 'भोजपुरी',
},
bi: {
nativeName: 'Bislama',
},
bm: {
nativeName: 'bamanankan',
},
bn: {
nativeName: 'বাংলা',
},
bo: {
nativeName: 'བོད་ཡིག',
},
br: {
nativeName: 'brezhoneg',
},
bs: {
nativeName: 'bosanski jezik',
},
ca: {
nativeName: 'Català',
},
ce: {
nativeName: 'нохчийн мотт',
},
ch: {
nativeName: 'Chamoru',
},
co: {
nativeName: 'corsu',
},
cr: {
nativeName: 'ᓀᐦᐃᔭᐍᐏᐣ',
},
cs: {
nativeName: 'čeština',
},
cu: {
nativeName: 'ѩзыкъ словѣньскъ',
},
cv: {
nativeName: 'чӑваш чӗлхи',
},
cy: {
nativeName: 'Cymraeg',
},
da: {
nativeName: 'dansk',
},
de: {
nativeName: 'Deutsch',
},
dv: {
nativeName: 'Dhivehi',
},
dz: {
nativeName: 'རྫོང་ཁ',
},
ee: {
nativeName: 'Eʋegbe',
},
el: {
nativeName: 'Ελληνικά',
},
en: {
nativeName: 'English',
},
eo: {
nativeName: 'Esperanto',
},
es: {
nativeName: 'Español',
},
et: {
nativeName: 'eesti',
},
eu: {
nativeName: 'euskara',
},
fa: {
nativeName: 'فارسی',
},
ff: {
nativeName: 'Fulfulde',
},
fi: {
nativeName: 'suomi',
},
fj: {
nativeName: 'Vakaviti',
},
fo: {
nativeName: 'føroyskt',
},
fr: {
nativeName: 'Français',
},
fy: {
nativeName: 'Frysk',
},
ga: {
nativeName: 'Gaeilge',
},
gd: {
nativeName: 'Gàidhlig',
},
gl: {
nativeName: 'galego',
},
gu: {
nativeName: 'ગુજરાતી',
},
gv: {
nativeName: 'Gaelg',
},
ha: {
nativeName: 'هَوُسَ',
},
he: {
nativeName: 'עברית',
},
hi: {
nativeName: 'हिन्दी',
},
ho: {
nativeName: 'Hiri Motu',
},
hr: {
nativeName: 'Hrvatski',
},
ht: {
nativeName: 'Kreyòl ayisyen',
},
hu: {
nativeName: 'magyar',
},
hy: {
nativeName: 'Հայերեն',
},
hz: {
nativeName: 'Otjiherero',
},
ia: {
nativeName: 'Interlingua',
},
id: {
nativeName: 'Bahasa Indonesia',
},
ie: {
nativeName: 'Interlingue',
},
ig: {
nativeName: 'Asụsụ Igbo',
},
ii: {
nativeName: 'ꆈꌠ꒿ Nuosuhxop',
},
ik: {
nativeName: 'Iñupiaq',
},
io: {
nativeName: 'Ido',
},
is: {
nativeName: 'Íslenska',
},
it: {
nativeName: 'Italiano',
},
iu: {
nativeName: 'ᐃᓄᒃᑎᑐᑦ',
},
ja: {
nativeName: '日本語',
},
jv: {
nativeName: 'basa Jawa',
},
ka: {
nativeName: 'ქართული',
},
kg: {
nativeName: 'Kikongo',
},
ki: {
nativeName: 'Gĩkũyũ',
},
kj: {
nativeName: 'Kuanyama',
},
kk: {
nativeName: 'қазақ тілі',
},
kl: {
nativeName: 'kalaallisut',
},
km: {
nativeName: 'ខេមរភាសា',
},
kn: {
nativeName: 'ಕನ್ನಡ',
},
ko: {
nativeName: '한국어',
},
kr: {
nativeName: 'Kanuri',
},
ks: {
nativeName: 'कश्मीरी',
},
ku: {
nativeName: 'Kurmancî',
},
kv: {
nativeName: 'коми кыв',
},
kw: {
nativeName: 'Kernewek',
},
ky: {
nativeName: 'Кыргызча',
},
la: {
nativeName: 'latine',
},
lb: {
nativeName: 'Lëtzebuergesch',
},
lg: {
nativeName: 'Luganda',
},
li: {
nativeName: 'Limburgs',
},
ln: {
nativeName: 'Lingála',
},
lo: {
nativeName: 'ລາວ',
},
lt: {
nativeName: 'lietuvių kalba',
},
lu: {
nativeName: 'Tshiluba',
},
lv: {
nativeName: 'latviešu valoda',
},
mg: {
nativeName: 'fiteny malagasy',
},
mh: {
nativeName: 'Kajin M̧ajeļ',
},
mi: {
nativeName: 'te reo Māori',
},
mk: {
nativeName: 'македонски јазик',
},
ml: {
nativeName: 'മലയാളം',
},
mn: {
nativeName: 'Монгол хэл',
},
mr: {
nativeName: 'मराठी',
},
ms: {
nativeName: 'Bahasa Melayu',
},
'ms-Arab': {
nativeName: 'بهاس ملايو',
},
mt: {
nativeName: 'Malti',
},
my: {
nativeName: 'ဗမာစာ',
},
na: {
nativeName: 'Ekakairũ Naoero',
},
nb: {
nativeName: 'Norsk bokmål',
},
nd: {
nativeName: 'isiNdebele',
},
ne: {
nativeName: 'नेपाली',
},
ng: {
nativeName: 'Owambo',
},
nl: {
nativeName: 'Nederlands',
},
nn: {
nativeName: 'Norsk Nynorsk',
},
no: {
nativeName: 'Norsk',
},
nr: {
nativeName: 'isiNdebele',
},
nv: {
nativeName: 'Diné bizaad',
},
ny: {
nativeName: 'chiCheŵa',
},
oc: {
nativeName: 'occitan',
},
oj: {
nativeName: 'ᐊᓂᔑᓈᐯᒧᐎᓐ',
},
om: {
nativeName: 'Afaan Oromoo',
},
or: {
nativeName: 'ଓଡ଼ିଆ',
},
os: {
nativeName: 'ирон æвзаг',
},
pa: {
nativeName: 'ਪੰਜਾਬੀ',
},
pi: {
nativeName: 'पाऴि',
},
pl: {
nativeName: 'Polski',
},
ps: {
nativeName: 'پښتو',
},
pt: {
nativeName: 'Português',
},
qu: {
nativeName: 'Runa Simi',
},
rm: {
nativeName: 'rumantsch grischun',
},
rn: {
nativeName: 'Ikirundi',
},
ro: {
nativeName: 'Română',
},
ru: {
nativeName: 'Русский',
},
rw: {
nativeName: 'Ikinyarwanda',
},
sa: {
nativeName: 'संस्कृतम्',
},
sc: {
nativeName: 'sardu',
},
sd: {
nativeName: 'सिन्धी',
},
se: {
nativeName: 'Davvisámegiella',
},
sg: {
nativeName: 'yângâ tî sängö',
},
si: {
nativeName: 'සිංහල',
},
sk: {
nativeName: 'slovenčina',
},
sl: {
nativeName: 'slovenščina',
},
sn: {
nativeName: 'chiShona',
},
so: {
nativeName: 'Soomaaliga',
},
sq: {
nativeName: 'Shqip',
},
sr: {
nativeName: 'српски језик',
},
ss: {
nativeName: 'SiSwati',
},
st: {
nativeName: 'Sesotho',
},
su: {
nativeName: 'Basa Sunda',
},
sv: {
nativeName: 'Svenska',
},
sw: {
nativeName: 'Kiswahili',
},
ta: {
nativeName: 'தமிழ்',
},
te: {
nativeName: 'తెలుగు',
},
tg: {
nativeName: 'тоҷикӣ',
},
th: {
nativeName: 'ไทย',
},
ti: {
nativeName: 'ትግርኛ',
},
tk: {
nativeName: 'Türkmen',
},
tl: {
nativeName: 'Tagalog',
},
tn: {
nativeName: 'Setswana',
},
to: {
nativeName: 'faka Tonga',
},
tr: {
nativeName: 'Türkçe',
},
ts: {
nativeName: 'Xitsonga',
},
tt: {
nativeName: 'татар теле',
},
tw: {
nativeName: 'Twi',
},
ty: {
nativeName: 'Reo Tahiti',
},
ug: {
nativeName: 'ئۇيغۇرچە‎',
},
uk: {
nativeName: 'Українська',
},
ur: {
nativeName: 'اردو',
},
vi: {
nativeName: 'Tiếng Việt',
},
vo: {
nativeName: 'Volapük',
},
wa: {
nativeName: 'Walon',
},
wo: {
nativeName: 'Wollof',
},
xh: {
nativeName: 'isiXhosa',
},
yi: {
nativeName: 'ייִדיש',
},
zu: {
nativeName: 'isiZulu',
},
};
export const ISO_639_3 = {
ast: {
nativeName: 'Asturianu',
},
chr: {
nativeName: 'ᏣᎳᎩ ᎦᏬᏂᎯᏍᏗ',
},
ckb: {
nativeName: 'سۆرانی',
},
cnr: {
nativeName: 'crnogorski',
},
csb: {
nativeName: 'Kaszëbsczi',
},
gsw: {
nativeName: 'Schwiizertütsch',
},
jbo: {
nativeName: 'la .lojban.',
},
kab: {
nativeName: 'Taqbaylit',
},
ldn: {
nativeName: 'Láadan',
},
lfn: {
nativeName: 'lingua franca nova',
},
moh: {
nativeName: 'Kanienʼkéha',
},
nds: {
nativeName: 'Plattdüütsch',
},
pdc: {
nativeName: 'Pennsilfaani-Deitsch',
},
sco: {
nativeName: 'Scots',
},
sma: {
nativeName: 'Åarjelsaemien Gïele',
},
smj: {
nativeName: 'Julevsámegiella',
},
szl: {
nativeName: 'ślůnsko godka',
},
tok: {
nativeName: 'toki pona',
},
vai: {
nativeName: 'ꕙꔤ',
},
xal: {
nativeName: 'Хальмг келн',
},
zba: {
nativeName: 'باليبلن',
},
zgh: {
nativeName: 'ⵜⴰⵎⴰⵣⵉⵖⵜ',
},
};
export const ISO_639_1_REGIONAL = {
'zh-CN': {
nativeName: '简体中文',
},
'zh-HK': {
nativeName: '繁體中文(香港)',
},
'zh-TW': {
nativeName: '繁體中文(臺灣)',
},
'zh-YUE': {
nativeName: '廣東話',
},
};
export const SUPPORTED_POST_LOCALES = {
...ISO_639_1,
...ISO_639_3,
...ISO_639_1_REGIONAL,
};

View file

@ -66,6 +66,12 @@ export class MiNote {
}) })
public text: string | null; public text: string | null;
@Column('varchar', {
length: 10,
nullable: true,
})
public lang: string | null;
@Column('varchar', { @Column('varchar', {
length: 256, nullable: true, length: 256, nullable: true,
}) })

View file

@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { SUPPORTED_POST_LOCALES } from "@/misc/langmap.js";
export const packedNoteSchema = { export const packedNoteSchema = {
type: 'object', type: 'object',
@ -26,6 +27,11 @@ export const packedNoteSchema = {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
}, },
lang: {
type: 'string',
enum: [...Object.keys(SUPPORTED_POST_LOCALES)],
nullable: true,
},
cw: { cw: {
type: 'string', type: 'string',
optional: true, nullable: true, optional: true, nullable: true,

View file

@ -45,6 +45,7 @@ class NoteStream extends ReadableStream<Record<string, unknown>> {
return { return {
id: note.id, id: note.id,
text: note.text, text: note.text,
lang: note.lang,
createdAt: idService.parse(note.id).date.toISOString(), createdAt: idService.parse(note.id).date.toISOString(),
fileIds: note.fileIds, fileIds: note.fileIds,
files: files, files: files,

View file

@ -16,7 +16,7 @@ import type { MiLocalUser, MiUser } from '@/models/User.js';
import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js'; import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js';
import type { MiUserProfile } from '@/models/UserProfile.js'; import type { MiUserProfile } from '@/models/UserProfile.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { langmap } from '@/misc/langmap.js'; import { SUPPORTED_POST_LOCALES } from '@/misc/langmap.js';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
@ -150,7 +150,7 @@ export const paramDef = {
description: { ...descriptionSchema, nullable: true }, description: { ...descriptionSchema, nullable: true },
location: { ...locationSchema, nullable: true }, location: { ...locationSchema, nullable: true },
birthday: { ...birthdaySchema, nullable: true }, birthday: { ...birthdaySchema, nullable: true },
lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true }, lang: { type: 'string', enum: [null, ...Object.keys(SUPPORTED_POST_LOCALES)] as string[], nullable: true },
avatarId: { type: 'string', format: 'misskey:id', nullable: true }, avatarId: { type: 'string', format: 'misskey:id', nullable: true },
avatarDecorations: { type: 'array', maxItems: 16, items: { avatarDecorations: { type: 'array', maxItems: 16, items: {
type: 'object', type: 'object',

View file

@ -23,6 +23,7 @@ import { isQuote, isRenote } from '@/misc/is-renote.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { LoggerService } from '@/core/LoggerService.js'; import { LoggerService } from '@/core/LoggerService.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
import { SUPPORTED_POST_LOCALES } from "@/misc/langmap.js";
export const meta = { export const meta = {
tags: ['notes'], tags: ['notes'],
@ -210,6 +211,11 @@ export const paramDef = {
maxLength: MAX_NOTE_TEXT_LENGTH, maxLength: MAX_NOTE_TEXT_LENGTH,
nullable: true, nullable: true,
}, },
lang: {
type: "string",
enum: Object.keys(SUPPORTED_POST_LOCALES),
nullable: true,
},
fileIds: { fileIds: {
type: 'array', type: 'array',
uniqueItems: true, uniqueItems: true,
@ -484,6 +490,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
} : 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

@ -375,6 +375,7 @@ export type NoteCreateOption = {
scheduledAt?: Date | null; scheduledAt?: Date | null;
name?: string | null; name?: string | null;
text?: string | null; text?: string | null;
lang?: string | null;
reply?: MiNote | null; reply?: MiNote | null;
renote?: MiNote | null; renote?: MiNote | null;
files?: MiDriveFile[] | null; files?: MiDriveFile[] | null;

View file

@ -12,8 +12,15 @@ SPDX-License-Identifier: AGPL-3.0-only
:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }" :style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }"
@contextmenu.self="e => e.preventDefault()" @contextmenu.self="e => e.preventDefault()"
> >
<div v-if="search" style="padding: 5px 6px;">
<MkInput v-model="searchbar" autofocus>
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
</div>
<template v-for="(item, i) in (items2 ?? [])"> <template v-for="(item, i) in (items2 ?? [])">
<div v-if="item.type === 'divider'" role="separator" :class="$style.divider"></div> <div v-if="item.type === 'divider'" role="separator" :class="$style.divider">
<span v-if="item.text" style="opacity: 0.7; font-size: smaller;">{{ item.text }}</span>
</div>
<span v-else-if="item.type === 'label'" role="menuitem" :class="[$style.label, $style.item]"> <span v-else-if="item.type === 'label'" role="menuitem" :class="[$style.label, $style.item]">
<span style="opacity: 0.7;">{{ item.text }}</span> <span style="opacity: 0.7;">{{ item.text }}</span>
</span> </span>
@ -103,6 +110,7 @@ const childrenCache = new WeakMap<MenuParent, MenuItem[]>();
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import MkInput from "@/components/MkInput.vue";
const XChild = defineAsyncComponent(() => import('./MkMenu.child.vue')); const XChild = defineAsyncComponent(() => import('./MkMenu.child.vue'));
const props = defineProps<{ const props = defineProps<{
@ -112,6 +120,7 @@ const props = defineProps<{
align?: 'center' | string; align?: 'center' | string;
width?: number; width?: number;
maxHeight?: number; maxHeight?: number;
search?: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -119,6 +128,8 @@ const emit = defineEmits<{
(ev: 'hide'): void; (ev: 'hide'): void;
}>(); }>();
const searchbar = ref<string | null>('');
const itemsEl = shallowRef<HTMLDivElement>(); const itemsEl = shallowRef<HTMLDivElement>();
const items2 = ref<InnerMenuItem[]>(); const items2 = ref<InnerMenuItem[]>();
@ -135,13 +146,17 @@ const childShowingItem = ref<MenuItem | null>();
let preferClick = isTouchUsing || props.asDrawer; let preferClick = isTouchUsing || props.asDrawer;
watch(() => props.items, () => { const filteredItems = ref<InnerMenuItem[]>([]);
const items = [...props.items].filter(item => item !== undefined) as (NonNullable<MenuItem> | MenuPending)[];
watch(
() => [props.items, searchbar.value],
() => {
let items = [...props.items].filter(item => item !== undefined) as (NonNullable<MenuItem> | MenuPending)[];
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
const item = items[i]; const item = items[i];
if ('then' in item) { // if item is Promise if ('then' in item) { // if item is a Promise
items[i] = { type: 'pending' }; items[i] = { type: 'pending' };
item.then(actualItem => { item.then(actualItem => {
if (items2.value?.[i]) items2.value[i] = actualItem; if (items2.value?.[i]) items2.value[i] = actualItem;
@ -149,10 +164,20 @@ watch(() => props.items, () => {
} }
} }
items2.value = items as InnerMenuItem[]; // Apply the search filter
}, { filteredItems.value = items.filter(item => {
immediate: true, // Modify the condition based on your data structure
}); if ('text' in item) {
return item.text.toLowerCase().includes(searchbar.value.toLowerCase());
}
return true;
}) as InnerMenuItem[];
// console.log(filteredItems.value)
items2.value = filteredItems.value;
},
{ immediate: true }
);
const childMenu = ref<MenuItem[] | null>(); const childMenu = ref<MenuItem[] | null>();
const childTarget = shallowRef<HTMLElement | null>(); const childTarget = shallowRef<HTMLElement | null>();
@ -511,8 +536,8 @@ onBeforeUnmount(() => {
} }
.divider { .divider {
margin: 8px 0; margin: 8px 8px;
border-top: solid 0.5px var(--divider); border-bottom: solid 0.5px var(--divider);
} }
.radio { .radio {

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<MkModal ref="modal" v-slot="{ type, maxHeight }" :manualShowing="manualShowing" :zPriority="'high'" :src="src" :transparentBg="true" @click="click" @close="onModalClose" @closed="onModalClosed"> <MkModal ref="modal" v-slot="{ type, maxHeight }" :manualShowing="manualShowing" :zPriority="'high'" :src="src" :transparentBg="true" @click="click" @close="onModalClose" @closed="onModalClosed">
<MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :asDrawer="type === 'drawer'" :class="{ [$style.drawer]: type === 'drawer' }" @close="onMenuClose" @hide="hide"/> <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :asDrawer="type === 'drawer'" :search="search" :class="{ [$style.drawer]: type === 'drawer' }" @close="onMenuClose" @hide="hide"/>
</MkModal> </MkModal>
</template> </template>
@ -21,6 +21,7 @@ defineProps<{
width?: number; width?: number;
viaKeyboard?: boolean; viaKeyboard?: boolean;
src?: any; src?: any;
search?: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{

View file

@ -41,6 +41,22 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="reactionAcceptance === 'likeOnlyForRemote'"><i class="ti ti-heart-plus"></i></span> <span v-else-if="reactionAcceptance === 'likeOnlyForRemote'"><i class="ti ti-heart-plus"></i></span>
<span v-else><i class="ti ti-icons"></i></span> <span v-else><i class="ti ti-icons"></i></span>
</button> </button>
<button
v-click-anime
ref="languageButton"
v-tooltip="i18n.ts.language"
class="_button"
:class="[$style.headerRightItem]"
@click="setLanguage"
>
<i
v-if="language === '' || language == null"
class="ti ti-language"
></i>
<span v-else style="font-weight: bold">
{{ language.split("-")[0] }}
</span>
</button>
<button v-if="!props.instant" v-click-anime v-tooltip="i18n.ts.drafts" class="_button" :class="$style.headerRightItem" @click="openDrafts"> <button v-if="!props.instant" v-click-anime v-tooltip="i18n.ts.drafts" class="_button" :class="$style.headerRightItem" @click="openDrafts">
<i class="ti ti-pencil"></i> <i class="ti ti-pencil"></i>
</button> </button>
@ -158,6 +174,7 @@ import { miLocalStorage } from '@/local-storage.js';
import { dateTimeFormat } from '@/scripts/intl-const.js'; import { dateTimeFormat } from '@/scripts/intl-const.js';
import { claimAchievement } from '@/scripts/achievements.js'; import { claimAchievement } from '@/scripts/achievements.js';
import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js'; import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js';
import { SUPPORTED_POST_LOCALES } from "@/scripts/langmap";
const $i = signinRequired(); const $i = signinRequired();
@ -205,6 +222,7 @@ const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
const cwInputEl = shallowRef<HTMLInputElement | null>(null); const cwInputEl = shallowRef<HTMLInputElement | null>(null);
const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null); const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
const visibilityButton = shallowRef<HTMLElement>(); const visibilityButton = shallowRef<HTMLElement>();
const languageButton = shallowRef<HTMLElement | null>(null);
const posting = ref(false); const posting = ref(false);
const posted = ref(false); const posted = ref(false);
@ -243,6 +261,12 @@ const imeText = ref('');
const showingOptions = ref(false); const showingOptions = ref(false);
const textAreaReadOnly = ref(false); const textAreaReadOnly = ref(false);
let language = ref<string | null>(
props.initialLanguage ??
defaultStore.state.recentlyUsedPostLanguages[0] ??
localStorage.getItem("lang")?.split("-")[0],
);
const draftKey = computed((): string => { const draftKey = computed((): string => {
let key = channel.value ? `channel:${channel.value.id}` : ''; let key = channel.value ? `channel:${channel.value.id}` : '';
@ -591,6 +615,69 @@ async function toggleReactionAcceptance() {
reactionAcceptance.value = select.result; reactionAcceptance.value = select.result;
} }
function setLanguage() {
const langs = Object.keys(SUPPORTED_POST_LOCALES);
const recentlyUsed = defaultStore.state.recentlyUsedPostLanguages;
const actions: Array<MenuItem | null> = [
{ type: 'divider', text: 'Recent Languages' },
// Current language
language.value
? {
text: SUPPORTED_POST_LOCALES[language.value].nativeName,
danger: false,
active: true,
action: () => {},
}
: null,
// Recently used languages (excluding current)
...recentlyUsed
.filter(lang => lang !== language.value && langs.includes(lang))
.map(lang => ({
text: SUPPORTED_POST_LOCALES[lang].nativeName,
danger: false,
active: false,
action: () => {
language.value = lang;
},
})),
// Separator if there are recently used languages
recentlyUsed.some(lang => lang !== language.value && langs.includes(lang))
? { type: 'divider' }
: null,
{
text: 'No Language',
danger: false,
active: false,
action: () => {
language.value = null;
},
},
// All other languages not in recently used and not the current language
...langs
.filter(
lang => lang !== language.value && !recentlyUsed.includes(lang)
)
.map(lang => ({
text: SUPPORTED_POST_LOCALES[lang].nativeName,
danger: false,
active: false,
action: () => {
language.value = lang;
},
})),
].filter(Boolean); // Remove null values
if (languageButton) {
os.popupMenu(actions, languageButton, { search: true });
}
}
function pushVisibleUser(user: Misskey.entities.UserDetailed) { function pushVisibleUser(user: Misskey.entities.UserDetailed) {
if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) { if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
visibleUsers.value.push(user); visibleUsers.value.push(user);
@ -772,6 +859,7 @@ function saveDraft() {
} : undefined, } : undefined,
data: { data: {
text: text.value, text: text.value,
lang: language.value,
useCw: useCw.value, useCw: useCw.value,
cw: cw.value, cw: cw.value,
visibility: visibility.value, visibility: visibility.value,
@ -843,6 +931,7 @@ function loadDraft(exactMatch = false) {
scheduledTime.value = null; scheduledTime.value = null;
} }
text.value = draft.value.data.text ?? ''; text.value = draft.value.data.text ?? '';
language.value = draft.value.data.language ?? '';
useCw.value = draft.value.data.useCw; useCw.value = draft.value.data.useCw;
cw.value = draft.value.data.cw; cw.value = draft.value.data.cw;
visibility.value = draft.value.data.visibility; visibility.value = draft.value.data.visibility;
@ -940,6 +1029,7 @@ async function post(ev?: MouseEvent) {
let postData = { let postData = {
text: text.value === '' ? null : text.value, text: text.value === '' ? null : text.value,
lang: language.value ?? undefined,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
fileIds: files.value.length > 0 ? files.value.filter(f => f?.id).map(f => f.id) : undefined, fileIds: files.value.length > 0 ? files.value.filter(f => f?.id).map(f => f.id) : undefined,
replyId: reply.value ? reply.value.id : undefined, replyId: reply.value ? reply.value.id : undefined,
@ -1048,6 +1138,27 @@ async function post(ev?: MouseEvent) {
claimAchievement('postedAt0min0sec'); claimAchievement('postedAt0min0sec');
} }
}); });
if (language.value) {
const languages = Object.keys(SUPPORTED_POST_LOCALES);
const maxLength = 6;
defaultStore.set(
"recentlyUsedPostLanguages",
[language.value]
.concat(
defaultStore.state.recentlyUsedPostLanguages.filter(
(lang) => {
return (
lang !== language.value &&
languages.includes(lang)
);
},
),
)
.slice(0, maxLength),
);
}
}).catch(err => { }).catch(err => {
posting.value = false; posting.value = false;
os.alert({ os.alert({
@ -1144,6 +1255,7 @@ onMounted(() => {
if (props.initialNote) { if (props.initialNote) {
const init = props.initialNote; const init = props.initialNote;
text.value = init.text ? init.text : ''; text.value = init.text ? init.text : '';
language.value = init.lang;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
files.value = init.files?.filter(f => f?.id && f.type && f.name) ?? []; files.value = init.files?.filter(f => f?.id && f.type && f.name) ?? [];
cw.value = init.cw ?? null; cw.value = init.cw ?? null;

View file

@ -25,6 +25,7 @@ const props = withDefaults(defineProps<{
initialText?: string; initialText?: string;
initialCw?: string; initialCw?: string;
initialVisibility?: (typeof Misskey.noteVisibilities)[number]; initialVisibility?: (typeof Misskey.noteVisibilities)[number];
initialLanguage?: typeof misskey.languages;
initialFiles?: Misskey.entities.DriveFile[]; initialFiles?: Misskey.entities.DriveFile[];
initialLocalOnly?: boolean; initialLocalOnly?: boolean;
initialVisibleUsers?: Misskey.entities.UserDetailed[]; initialVisibleUsers?: Misskey.entities.UserDetailed[];

View file

@ -708,6 +708,7 @@ export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | n
width?: number; width?: number;
viaKeyboard?: boolean; viaKeyboard?: boolean;
onClosing?: () => void; onClosing?: () => void;
search: boolean;
}): Promise<void> { }): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
let dispose; let dispose;
@ -717,6 +718,7 @@ export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | n
width: options?.width, width: options?.width,
align: options?.align, align: options?.align,
viaKeyboard: options?.viaKeyboard, viaKeyboard: options?.viaKeyboard,
search: options?.search,
}, { }, {
closed: () => { closed: () => {
resolve(); resolve();

View file

@ -669,3 +669,631 @@ export const langmap = {
nativeName: 'isiZulu', nativeName: 'isiZulu',
}, },
}; };
export const ISO_639_1 = {
aa: {
nativeName: 'Afaraf',
},
ab: {
nativeName: 'аҧсуа бызшәа',
},
ae: {
nativeName: 'avesta',
},
af: {
nativeName: 'Afrikaans',
},
ak: {
nativeName: 'Akan',
},
am: {
nativeName: 'አማርኛ',
},
an: {
nativeName: 'aragonés',
},
ar: {
nativeName: 'اللغة العربية',
},
as: {
nativeName: 'অসমীয়া',
},
av: {
nativeName: 'авар мацӀ',
},
ay: {
nativeName: 'aymar aru',
},
az: {
nativeName: 'azərbaycan dili',
},
ba: {
nativeName: 'башҡорт теле',
},
be: {
nativeName: 'беларуская мова',
},
bg: {
nativeName: 'български език',
},
bh: {
nativeName: 'भोजपुरी',
},
bi: {
nativeName: 'Bislama',
},
bm: {
nativeName: 'bamanankan',
},
bn: {
nativeName: 'বাংলা',
},
bo: {
nativeName: 'བོད་ཡིག',
},
br: {
nativeName: 'brezhoneg',
},
bs: {
nativeName: 'bosanski jezik',
},
ca: {
nativeName: 'Català',
},
ce: {
nativeName: 'нохчийн мотт',
},
ch: {
nativeName: 'Chamoru',
},
co: {
nativeName: 'corsu',
},
cr: {
nativeName: 'ᓀᐦᐃᔭᐍᐏᐣ',
},
cs: {
nativeName: 'čeština',
},
cu: {
nativeName: 'ѩзыкъ словѣньскъ',
},
cv: {
nativeName: 'чӑваш чӗлхи',
},
cy: {
nativeName: 'Cymraeg',
},
da: {
nativeName: 'dansk',
},
de: {
nativeName: 'Deutsch',
},
dv: {
nativeName: 'Dhivehi',
},
dz: {
nativeName: 'རྫོང་ཁ',
},
ee: {
nativeName: 'Eʋegbe',
},
el: {
nativeName: 'Ελληνικά',
},
en: {
nativeName: 'English',
},
eo: {
nativeName: 'Esperanto',
},
es: {
nativeName: 'Español',
},
et: {
nativeName: 'eesti',
},
eu: {
nativeName: 'euskara',
},
fa: {
nativeName: 'فارسی',
},
ff: {
nativeName: 'Fulfulde',
},
fi: {
nativeName: 'suomi',
},
fj: {
nativeName: 'Vakaviti',
},
fo: {
nativeName: 'føroyskt',
},
fr: {
nativeName: 'Français',
},
fy: {
nativeName: 'Frysk',
},
ga: {
nativeName: 'Gaeilge',
},
gd: {
nativeName: 'Gàidhlig',
},
gl: {
nativeName: 'galego',
},
gu: {
nativeName: 'ગુજરાતી',
},
gv: {
nativeName: 'Gaelg',
},
ha: {
nativeName: 'هَوُسَ',
},
he: {
nativeName: 'עברית',
},
hi: {
nativeName: 'हिन्दी',
},
ho: {
nativeName: 'Hiri Motu',
},
hr: {
nativeName: 'Hrvatski',
},
ht: {
nativeName: 'Kreyòl ayisyen',
},
hu: {
nativeName: 'magyar',
},
hy: {
nativeName: 'Հայերեն',
},
hz: {
nativeName: 'Otjiherero',
},
ia: {
nativeName: 'Interlingua',
},
id: {
nativeName: 'Bahasa Indonesia',
},
ie: {
nativeName: 'Interlingue',
},
ig: {
nativeName: 'Asụsụ Igbo',
},
ii: {
nativeName: 'ꆈꌠ꒿ Nuosuhxop',
},
ik: {
nativeName: 'Iñupiaq',
},
io: {
nativeName: 'Ido',
},
is: {
nativeName: 'Íslenska',
},
it: {
nativeName: 'Italiano',
},
iu: {
nativeName: 'ᐃᓄᒃᑎᑐᑦ',
},
ja: {
nativeName: '日本語',
},
jv: {
nativeName: 'basa Jawa',
},
ka: {
nativeName: 'ქართული',
},
kg: {
nativeName: 'Kikongo',
},
ki: {
nativeName: 'Gĩkũyũ',
},
kj: {
nativeName: 'Kuanyama',
},
kk: {
nativeName: 'қазақ тілі',
},
kl: {
nativeName: 'kalaallisut',
},
km: {
nativeName: 'ខេមរភាសា',
},
kn: {
nativeName: 'ಕನ್ನಡ',
},
ko: {
nativeName: '한국어',
},
kr: {
nativeName: 'Kanuri',
},
ks: {
nativeName: 'कश्मीरी',
},
ku: {
nativeName: 'Kurmancî',
},
kv: {
nativeName: 'коми кыв',
},
kw: {
nativeName: 'Kernewek',
},
ky: {
nativeName: 'Кыргызча',
},
la: {
nativeName: 'latine',
},
lb: {
nativeName: 'Lëtzebuergesch',
},
lg: {
nativeName: 'Luganda',
},
li: {
nativeName: 'Limburgs',
},
ln: {
nativeName: 'Lingála',
},
lo: {
nativeName: 'ລາວ',
},
lt: {
nativeName: 'lietuvių kalba',
},
lu: {
nativeName: 'Tshiluba',
},
lv: {
nativeName: 'latviešu valoda',
},
mg: {
nativeName: 'fiteny malagasy',
},
mh: {
nativeName: 'Kajin M̧ajeļ',
},
mi: {
nativeName: 'te reo Māori',
},
mk: {
nativeName: 'македонски јазик',
},
ml: {
nativeName: 'മലയാളം',
},
mn: {
nativeName: 'Монгол хэл',
},
mr: {
nativeName: 'मराठी',
},
ms: {
nativeName: 'Bahasa Melayu',
},
'ms-Arab': {
nativeName: 'بهاس ملايو',
},
mt: {
nativeName: 'Malti',
},
my: {
nativeName: 'ဗမာစာ',
},
na: {
nativeName: 'Ekakairũ Naoero',
},
nb: {
nativeName: 'Norsk bokmål',
},
nd: {
nativeName: 'isiNdebele',
},
ne: {
nativeName: 'नेपाली',
},
ng: {
nativeName: 'Owambo',
},
nl: {
nativeName: 'Nederlands',
},
nn: {
nativeName: 'Norsk Nynorsk',
},
no: {
nativeName: 'Norsk',
},
nr: {
nativeName: 'isiNdebele',
},
nv: {
nativeName: 'Diné bizaad',
},
ny: {
nativeName: 'chiCheŵa',
},
oc: {
nativeName: 'occitan',
},
oj: {
nativeName: 'ᐊᓂᔑᓈᐯᒧᐎᓐ',
},
om: {
nativeName: 'Afaan Oromoo',
},
or: {
nativeName: 'ଓଡ଼ିଆ',
},
os: {
nativeName: 'ирон æвзаг',
},
pa: {
nativeName: 'ਪੰਜਾਬੀ',
},
pi: {
nativeName: 'पाऴि',
},
pl: {
nativeName: 'Polski',
},
ps: {
nativeName: 'پښتو',
},
pt: {
nativeName: 'Português',
},
qu: {
nativeName: 'Runa Simi',
},
rm: {
nativeName: 'rumantsch grischun',
},
rn: {
nativeName: 'Ikirundi',
},
ro: {
nativeName: 'Română',
},
ru: {
nativeName: 'Русский',
},
rw: {
nativeName: 'Ikinyarwanda',
},
sa: {
nativeName: 'संस्कृतम्',
},
sc: {
nativeName: 'sardu',
},
sd: {
nativeName: 'सिन्धी',
},
se: {
nativeName: 'Davvisámegiella',
},
sg: {
nativeName: 'yângâ tî sängö',
},
si: {
nativeName: 'සිංහල',
},
sk: {
nativeName: 'slovenčina',
},
sl: {
nativeName: 'slovenščina',
},
sn: {
nativeName: 'chiShona',
},
so: {
nativeName: 'Soomaaliga',
},
sq: {
nativeName: 'Shqip',
},
sr: {
nativeName: 'српски језик',
},
ss: {
nativeName: 'SiSwati',
},
st: {
nativeName: 'Sesotho',
},
su: {
nativeName: 'Basa Sunda',
},
sv: {
nativeName: 'Svenska',
},
sw: {
nativeName: 'Kiswahili',
},
ta: {
nativeName: 'தமிழ்',
},
te: {
nativeName: 'తెలుగు',
},
tg: {
nativeName: 'тоҷикӣ',
},
th: {
nativeName: 'ไทย',
},
ti: {
nativeName: 'ትግርኛ',
},
tk: {
nativeName: 'Türkmen',
},
tl: {
nativeName: 'Tagalog',
},
tn: {
nativeName: 'Setswana',
},
to: {
nativeName: 'faka Tonga',
},
tr: {
nativeName: 'Türkçe',
},
ts: {
nativeName: 'Xitsonga',
},
tt: {
nativeName: 'татар теле',
},
tw: {
nativeName: 'Twi',
},
ty: {
nativeName: 'Reo Tahiti',
},
ug: {
nativeName: 'ئۇيغۇرچە‎',
},
uk: {
nativeName: 'Українська',
},
ur: {
nativeName: 'اردو',
},
vi: {
nativeName: 'Tiếng Việt',
},
vo: {
nativeName: 'Volapük',
},
wa: {
nativeName: 'Walon',
},
wo: {
nativeName: 'Wollof',
},
xh: {
nativeName: 'isiXhosa',
},
yi: {
nativeName: 'ייִדיש',
},
zu: {
nativeName: 'isiZulu',
},
};
export const ISO_639_3 = {
ast: {
nativeName: 'Asturianu',
},
chr: {
nativeName: 'ᏣᎳᎩ ᎦᏬᏂᎯᏍᏗ',
},
ckb: {
nativeName: 'سۆرانی',
},
cnr: {
nativeName: 'crnogorski',
},
csb: {
nativeName: 'Kaszëbsczi',
},
gsw: {
nativeName: 'Schwiizertütsch',
},
jbo: {
nativeName: 'la .lojban.',
},
kab: {
nativeName: 'Taqbaylit',
},
ldn: {
nativeName: 'Láadan',
},
lfn: {
nativeName: 'lingua franca nova',
},
moh: {
nativeName: 'Kanienʼkéha',
},
nds: {
nativeName: 'Plattdüütsch',
},
pdc: {
nativeName: 'Pennsilfaani-Deitsch',
},
sco: {
nativeName: 'Scots',
},
sma: {
nativeName: 'Åarjelsaemien Gïele',
},
smj: {
nativeName: 'Julevsámegiella',
},
szl: {
nativeName: 'ślůnsko godka',
},
tok: {
nativeName: 'toki pona',
},
vai: {
nativeName: 'ꕙꔤ',
},
xal: {
nativeName: 'Хальмг келн',
},
zba: {
nativeName: 'باليبلن',
},
zgh: {
nativeName: 'ⵜⴰⵎⴰⵣⵉⵖⵜ',
},
};
export const ISO_639_1_REGIONAL = {
'zh-CN': {
nativeName: '简体中文',
},
'zh-HK': {
nativeName: '繁體中文(香港)',
},
'zh-TW': {
nativeName: '繁體中文(臺灣)',
},
'zh-YUE': {
nativeName: '廣東話',
},
};
export const SUPPORTED_POST_LOCALES = {
...ISO_639_1,
...ISO_639_3,
...ISO_639_1_REGIONAL,
};

View file

@ -133,6 +133,11 @@ export const defaultStore = markRaw(new Storage('base', {
default: [] as string[], default: [] as string[],
}, },
recentlyUsedPostLanguages: {
where: "account",
default: [] as string[],
},
menu: { menu: {
where: 'deviceAccount', where: 'deviceAccount',
default: [ default: [

View file

@ -2391,6 +2391,9 @@ type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200
// @public (undocumented) // @public (undocumented)
type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json']; type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json'];
// @public (undocumented)
export const languages: readonly ["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"];
// @public (undocumented) // @public (undocumented)
type MeDetailed = components['schemas']['MeDetailed']; type MeDetailed = components['schemas']['MeDetailed'];

View file

@ -4244,6 +4244,8 @@ export type components = {
/** Format: date-time */ /** Format: date-time */
deletedAt?: string | null; deletedAt?: string | null;
text: string | null; text: string | null;
/** @enum {string|null} */
lang: 'ach' | 'ady' | 'af' | 'af-NA' | 'af-ZA' | 'ak' | 'ar' | 'ar-AR' | 'ar-MA' | 'ar-SA' | 'ay-BO' | 'az' | 'az-AZ' | 'be-BY' | 'bg' | 'bg-BG' | 'bn' | 'bn-IN' | 'bn-BD' | 'br' | 'bs-BA' | 'ca' | 'ca-ES' | 'cak' | 'ck-US' | 'cs' | 'cs-CZ' | 'cy' | 'cy-GB' | 'da' | 'da-DK' | 'de' | 'de-AT' | 'de-DE' | 'de-CH' | 'dsb' | 'el' | 'el-GR' | 'en' | 'en-GB' | 'en-AU' | 'en-CA' | 'en-IE' | 'en-IN' | 'en-PI' | 'en-SG' | 'en-UD' | 'en-US' | 'en-ZA' | 'en@pirate' | 'eo' | 'eo-EO' | 'es' | 'es-AR' | 'es-419' | 'es-CL' | 'es-CO' | 'es-EC' | 'es-ES' | 'es-LA' | 'es-NI' | 'es-MX' | 'es-US' | 'es-VE' | 'et' | 'et-EE' | 'eu' | 'eu-ES' | 'fa' | 'fa-IR' | 'fb-LT' | 'ff' | 'fi' | 'fi-FI' | 'fo' | 'fo-FO' | 'fr' | 'fr-CA' | 'fr-FR' | 'fr-BE' | 'fr-CH' | 'fy-NL' | 'ga' | 'ga-IE' | 'gd' | 'gl' | 'gl-ES' | 'gn-PY' | 'gu-IN' | 'gv' | 'gx-GR' | 'he' | 'he-IL' | 'hi' | 'hi-IN' | 'hr' | 'hr-HR' | 'hsb' | 'ht' | 'hu' | 'hu-HU' | 'hy' | 'hy-AM' | 'id' | 'id-ID' | 'is' | 'is-IS' | 'it' | 'it-IT' | 'ja' | 'ja-JP' | 'jv-ID' | 'ka-GE' | 'kk-KZ' | 'km' | 'kl' | 'km-KH' | 'kab' | 'kn' | 'kn-IN' | 'ko' | 'ko-KR' | 'ku-TR' | 'kw' | 'la' | 'la-VA' | 'lb' | 'li-NL' | 'lt' | 'lt-LT' | 'lv' | 'lv-LV' | 'mai' | 'mg-MG' | 'mk' | 'mk-MK' | 'ml' | 'ml-IN' | 'mn-MN' | 'mr' | 'mr-IN' | 'ms' | 'ms-MY' | 'mt' | 'mt-MT' | 'my' | 'no' | 'nb' | 'nb-NO' | 'ne' | 'ne-NP' | 'nl' | 'nl-BE' | 'nl-NL' | 'nn-NO' | 'oc' | 'or-IN' | 'pa' | 'pa-IN' | 'pl' | 'pl-PL' | 'ps-AF' | 'pt' | 'pt-BR' | 'pt-PT' | 'qu-PE' | 'rm-CH' | 'ro' | 'ro-RO' | 'ru' | 'ru-RU' | 'sa-IN' | 'se-NO' | 'sh' | 'si-LK' | 'sk' | 'sk-SK' | 'sl' | 'sl-SI' | 'so-SO' | 'sq' | 'sq-AL' | 'sr' | 'sr-RS' | 'su' | 'sv' | 'sv-SE' | 'sw' | 'sw-KE' | 'ta' | 'ta-IN' | 'te' | 'te-IN' | 'tg' | 'tg-TJ' | 'th' | 'th-TH' | 'fil' | 'tlh' | 'tr' | 'tr-TR' | 'tt-RU' | 'uk' | 'uk-UA' | 'ur' | 'ur-PK' | 'uz' | 'uz-UZ' | 'vi' | 'vi-VN' | 'xh-ZA' | 'yi' | 'yi-DE' | 'zh' | 'zh-Hans' | 'zh-Hant' | 'zh-CN' | 'zh-HK' | 'zh-SG' | 'zh-TW' | 'zu-ZA';
cw?: string | null; cw?: string | null;
/** Format: id */ /** Format: id */
userId: string; userId: string;
@ -23564,6 +23566,8 @@ export type operations = {
/** Format: misskey:id */ /** Format: misskey:id */
channelId?: string | null; channelId?: string | null;
text?: string | null; text?: string | null;
/** @enum {string|null} */
lang?: 'ach' | 'ady' | 'af' | 'af-NA' | 'af-ZA' | 'ak' | 'ar' | 'ar-AR' | 'ar-MA' | 'ar-SA' | 'ay-BO' | 'az' | 'az-AZ' | 'be-BY' | 'bg' | 'bg-BG' | 'bn' | 'bn-IN' | 'bn-BD' | 'br' | 'bs-BA' | 'ca' | 'ca-ES' | 'cak' | 'ck-US' | 'cs' | 'cs-CZ' | 'cy' | 'cy-GB' | 'da' | 'da-DK' | 'de' | 'de-AT' | 'de-DE' | 'de-CH' | 'dsb' | 'el' | 'el-GR' | 'en' | 'en-GB' | 'en-AU' | 'en-CA' | 'en-IE' | 'en-IN' | 'en-PI' | 'en-SG' | 'en-UD' | 'en-US' | 'en-ZA' | 'en@pirate' | 'eo' | 'eo-EO' | 'es' | 'es-AR' | 'es-419' | 'es-CL' | 'es-CO' | 'es-EC' | 'es-ES' | 'es-LA' | 'es-NI' | 'es-MX' | 'es-US' | 'es-VE' | 'et' | 'et-EE' | 'eu' | 'eu-ES' | 'fa' | 'fa-IR' | 'fb-LT' | 'ff' | 'fi' | 'fi-FI' | 'fo' | 'fo-FO' | 'fr' | 'fr-CA' | 'fr-FR' | 'fr-BE' | 'fr-CH' | 'fy-NL' | 'ga' | 'ga-IE' | 'gd' | 'gl' | 'gl-ES' | 'gn-PY' | 'gu-IN' | 'gv' | 'gx-GR' | 'he' | 'he-IL' | 'hi' | 'hi-IN' | 'hr' | 'hr-HR' | 'hsb' | 'ht' | 'hu' | 'hu-HU' | 'hy' | 'hy-AM' | 'id' | 'id-ID' | 'is' | 'is-IS' | 'it' | 'it-IT' | 'ja' | 'ja-JP' | 'jv-ID' | 'ka-GE' | 'kk-KZ' | 'km' | 'kl' | 'km-KH' | 'kab' | 'kn' | 'kn-IN' | 'ko' | 'ko-KR' | 'ku-TR' | 'kw' | 'la' | 'la-VA' | 'lb' | 'li-NL' | 'lt' | 'lt-LT' | 'lv' | 'lv-LV' | 'mai' | 'mg-MG' | 'mk' | 'mk-MK' | 'ml' | 'ml-IN' | 'mn-MN' | 'mr' | 'mr-IN' | 'ms' | 'ms-MY' | 'mt' | 'mt-MT' | 'my' | 'no' | 'nb' | 'nb-NO' | 'ne' | 'ne-NP' | 'nl' | 'nl-BE' | 'nl-NL' | 'nn-NO' | 'oc' | 'or-IN' | 'pa' | 'pa-IN' | 'pl' | 'pl-PL' | 'ps-AF' | 'pt' | 'pt-BR' | 'pt-PT' | 'qu-PE' | 'rm-CH' | 'ro' | 'ro-RO' | 'ru' | 'ru-RU' | 'sa-IN' | 'se-NO' | 'sh' | 'si-LK' | 'sk' | 'sk-SK' | 'sl' | 'sl-SI' | 'so-SO' | 'sq' | 'sq-AL' | 'sr' | 'sr-RS' | 'su' | 'sv' | 'sv-SE' | 'sw' | 'sw-KE' | 'ta' | 'ta-IN' | 'te' | 'te-IN' | 'tg' | 'tg-TJ' | 'th' | 'th-TH' | 'fil' | 'tlh' | 'tr' | 'tr-TR' | 'tt-RU' | 'uk' | 'uk-UA' | 'ur' | 'ur-PK' | 'uz' | 'uz-UZ' | 'vi' | 'vi-VN' | 'xh-ZA' | 'yi' | 'yi-DE' | 'zh' | 'zh-Hans' | 'zh-Hant' | 'zh-CN' | 'zh-HK' | 'zh-SG' | 'zh-TW' | 'zu-ZA';
fileIds?: string[]; fileIds?: string[];
mediaIds?: string[]; mediaIds?: string[];
poll?: ({ poll?: ({

View file

@ -398,3 +398,97 @@ export type ModerationLogPayloads = {
mutualLinkSections: string; mutualLinkSections: string;
}; };
}; };
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

@ -15,6 +15,7 @@ export {
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 mutedNoteReasons = consts.mutedNoteReasons; export const mutedNoteReasons = consts.mutedNoteReasons;
export const followingVisibilities = consts.followingVisibilities; export const followingVisibilities = consts.followingVisibilities;
export const followersVisibilities = consts.followersVisibilities; export const followersVisibilities = consts.followersVisibilities;