feat: Add language metadata to notes

This commit is contained in:
Essem 2024-02-03 11:45:45 -06:00
parent 2fa0e238b7
commit a76d3cf861
No known key found for this signature in database
GPG key ID: 7D497397CC3A2A8C
20 changed files with 630 additions and 923 deletions

View file

@ -1265,6 +1265,7 @@ hemisphere: "Where are you located"
withSensitive: "Include notes with sensitive files"
userSaysSomethingSensitive: "Post by {name} contains sensitive content"
enableHorizontalSwipe: "Swipe to switch tabs"
noLanguage: "No language"
_bubbleGame:
howToPlay: "How to play"
_howToPlay:

View file

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

View file

@ -63,6 +63,7 @@ import { trackPromise } from '@/misc/promise-tracker.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { isNotNull } from '@/misc/is-not-null.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { langmap } from '@/misc/langmap.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -133,6 +134,7 @@ type Option = {
createdAt?: Date | null;
name?: string | null;
text?: string | null;
lang?: string | null;
reply?: MiNote | null;
renote?: MiNote | null;
files?: MiDriveFile[] | null;
@ -337,6 +339,13 @@ export class NoteCreateService implements OnApplicationShutdown {
data.text = null;
}
if (data.lang) {
if (!Object.keys(langmap).includes(data.lang.toLowerCase())) throw new Error('invalid param');
data.lang = data.lang.toLowerCase();
} else {
data.lang = null;
}
let tags = data.apHashtags;
let emojis = data.apEmojis;
let mentionedUsers = data.apMentions;
@ -579,6 +588,7 @@ export class NoteCreateService implements OnApplicationShutdown {
: null,
name: data.name,
text: data.text,
lang: data.lang,
hasPoll: data.poll != null,
cw: data.cw ?? null,
tags: tags.map(tag => normalizeForSearch(tag)),
@ -1004,7 +1014,7 @@ export class NoteCreateService implements OnApplicationShutdown {
removeOnComplete: true,
});
}
// Pack the note
const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true });

View file

@ -52,6 +52,7 @@ import { isReply } from '@/misc/is-reply.js';
import { trackPromise } from '@/misc/promise-tracker.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { langmap } from '@/misc/langmap.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention' | 'edited';
@ -122,6 +123,7 @@ type Option = {
createdAt?: Date | null;
name?: string | null;
text?: string | null;
lang?: string | null;
reply?: MiNote | null;
renote?: MiNote | null;
files?: MiDriveFile[] | null;
@ -358,6 +360,13 @@ export class NoteEditService implements OnApplicationShutdown {
data.text = null;
}
if (data.lang) {
if (!Object.keys(langmap).includes(data.lang.toLowerCase())) throw new Error('invalid param');
data.lang = data.lang.toLowerCase();
} else {
data.lang = null;
}
let tags = data.apHashtags;
let emojis = data.apEmojis;
let mentionedUsers = data.apMentions;
@ -420,6 +429,9 @@ export class NoteEditService implements OnApplicationShutdown {
if (oldnote.hasPoll !== !!data.poll) {
update.hasPoll = !!data.poll;
}
if (data.lang !== oldnote.lang) {
update.lang = data.lang;
}
const poll = await this.pollsRepository.findOneBy({ noteId: oldnote.id });
@ -434,6 +446,7 @@ export class NoteEditService implements OnApplicationShutdown {
oldText: oldnote.text || undefined,
newText: update.text || undefined,
cw: update.cw || undefined,
lang: update.lang || undefined,
fileIds: undefined,
oldDate: exists ? oldnote.updatedAt as Date : this.idService.parse(oldnote.id).date,
updatedAt: new Date(),
@ -453,6 +466,7 @@ export class NoteEditService implements OnApplicationShutdown {
: null,
name: data.name,
text: data.text,
lang: data.lang,
hasPoll: data.poll != null,
cw: data.cw ?? null,
tags: tags.map(tag => normalizeForSearch(tag)),

View file

@ -285,7 +285,7 @@ export class ApRendererService {
if (instance && instance.softwareName === 'pleroma') isMastodon = true;
}
}
const object: ILike = {
type: 'Like',
id: `${this.config.url}/likes/${noteReaction.id}`,
@ -454,6 +454,9 @@ export class ApRendererService {
mediaType: 'text/x.misskeymarkdown',
},
}),
contentMap: note.lang ? {
[note.lang]: content,
} : null,
_misskey_quote: quote,
quoteUrl: quote,
quoteUri: quote,
@ -746,6 +749,9 @@ export class ApRendererService {
mediaType: 'text/x.misskeymarkdown',
},
}),
contentMap: note.lang ? {
[note.lang]: content,
} : null,
_misskey_quote: quote,
quoteUrl: quote,
quoteUri: quote,

View file

@ -25,6 +25,7 @@ import { StatusError } from '@/misc/status-error.js';
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { checkHttps } from '@/misc/check-https.js';
import { langmap } from '@/misc/langmap.js';
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
import { ApLoggerService } from '../ApLoggerService.js';
import { ApMfmService } from '../ApMfmService.js';
@ -244,12 +245,21 @@ export class ApNoteService {
let text: string | null = null;
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
text = note.source.content;
} else if (note.contentMap != null) {
const entry = Object.entries(note.contentMap)[0];
text = this.apMfmService.htmlToMfm(entry[1], note.tag);
} else if (typeof note._misskey_content !== 'undefined') {
text = note._misskey_content;
} else if (typeof note.content === 'string') {
text = this.apMfmService.htmlToMfm(note.content, note.tag);
}
let lang: string | null = null;
if (note.contentMap != null) {
const key = Object.keys(note.contentMap)[0].toLowerCase();
lang = Object.keys(langmap).includes(key) ? key : null;
}
// vote
if (reply && reply.hasPoll) {
const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });
@ -290,6 +300,7 @@ export class ApNoteService {
name: note.name,
cw,
text,
lang,
localOnly: false,
visibility,
visibleUsers,
@ -452,12 +463,21 @@ export class ApNoteService {
let text: string | null = null;
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
text = note.source.content;
} else if (note.contentMap != null) {
const entry = Object.entries(note.contentMap)[0];
text = this.apMfmService.htmlToMfm(entry[1], note.tag);
} else if (typeof note._misskey_content !== 'undefined') {
text = note._misskey_content;
} else if (typeof note.content === 'string') {
text = this.apMfmService.htmlToMfm(note.content, note.tag);
}
let lang: string | null = null;
if (note.contentMap != null) {
const key = Object.keys(note.contentMap)[0].toLowerCase();
lang = Object.keys(langmap).includes(key) ? key : null;
}
// vote
if (reply && reply.hasPoll) {
const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });
@ -498,6 +518,7 @@ export class ApNoteService {
name: note.name,
cw,
text,
lang,
localOnly: false,
visibility,
visibleUsers,

View file

@ -21,6 +21,7 @@ export interface IObject {
inReplyTo?: any;
replies?: ICollection;
content?: string | null;
contentMap?: Obj | null;
startTime?: Date;
endTime?: Date;
icon?: any;

View file

@ -17,12 +17,12 @@ import { bindThis } from '@/decorators.js';
import { isNotNull } from '@/misc/is-not-null.js';
import { DebounceLoader } from '@/misc/loader.js';
import { IdService } from '@/core/IdService.js';
import type { Config } from '@/config.js';
import type { OnModuleInit } from '@nestjs/common';
import type { CustomEmojiService } from '../CustomEmojiService.js';
import type { ReactionService } from '../ReactionService.js';
import type { UserEntityService } from './UserEntityService.js';
import type { DriveFileEntityService } from './DriveFileEntityService.js';
import type { Config } from '@/config.js';
@Injectable()
export class NoteEntityService implements OnModuleInit {
@ -120,7 +120,7 @@ export class NoteEntityService implements OnModuleInit {
followerId: meId,
},
});
hide = !isFollowing;
} else {
// フォロワーかどうか
@ -373,6 +373,7 @@ export class NoteEntityService implements OnModuleInit {
uri: note.uri ?? undefined,
url: note.url ?? undefined,
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
lang: note.lang,
...(meId && Object.keys(note.reactions).length > 0 ? {
myReaction: this.populateMyReaction(note, meId, options?._hint_),
} : {}),

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { Entity, JoinColumn, Column, ManyToOne, PrimaryColumn, Index } from "typeorm";
import { Entity, JoinColumn, Column, ManyToOne, PrimaryColumn, Index } from 'typeorm';
import { id } from './util/id.js';
import { MiNote } from './Note.js';
import type { MiDriveFile } from './DriveFile.js';
@ -11,46 +11,52 @@ export class NoteEdit {
@Index()
@Column({
...id(),
comment: "The ID of note.",
comment: 'The ID of note.',
})
public noteId: MiNote["id"];
public noteId: MiNote['id'];
@ManyToOne((type) => MiNote, {
onDelete: "CASCADE",
onDelete: 'CASCADE',
})
@JoinColumn()
public note: MiNote | null;
@Column("text", {
@Column('text', {
nullable: true,
})
public oldText: string | null;
@Column("text", {
@Column('text', {
nullable: true,
})
public newText: string | null;
@Column("varchar", {
@Column('varchar', {
length: 512,
nullable: true,
})
public cw: string | null;
@Column('varchar', {
length: 10,
nullable: true,
})
public lang: string | null;
@Column({
...id(),
array: true,
default: "{}",
default: '{}',
})
public fileIds: MiDriveFile["id"][];
public fileIds: MiDriveFile['id'][];
@Column("timestamp with time zone", {
comment: "The updated date of the Note.",
@Column('timestamp with time zone', {
comment: 'The updated date of the Note.',
})
public updatedAt: Date;
@Column("timestamp with time zone", {
comment: "The old date from before the edit",
@Column('timestamp with time zone', {
comment: 'The old date from before the edit',
})
public oldDate: Date;
}

View file

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

View file

@ -20,6 +20,7 @@ import { isPureRenote } from '@/misc/is-pure-renote.js';
import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { langmap } from '@/misc/langmap.js';
import { ApiError } from '../../error.js';
export const meta = {
@ -136,6 +137,7 @@ export const paramDef = {
visibleUserIds: { type: 'array', uniqueItems: true, items: {
type: 'string', format: 'misskey:id',
} },
lang: { type: 'string', enum: Object.keys(langmap), nullable: true, maxLength: 10 },
cw: { type: 'string', nullable: true, minLength: 1, maxLength: 500 },
localOnly: { type: 'boolean', default: false },
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
@ -370,6 +372,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
} : undefined,
text: ps.text ?? undefined,
lang: ps.lang,
reply,
renote,
cw: ps.cw,

View file

@ -12,6 +12,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { NoteEditService } from '@/core/NoteEditService.js';
import { DI } from '@/di-symbols.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { langmap } from '@/misc/langmap.js';
import { ApiError } from '../../error.js';
export const meta = {
@ -164,6 +165,7 @@ export const paramDef = {
format: 'misskey:id',
},
},
lang: { type: 'string', enum: Object.keys(langmap), nullable: true, maxLength: 10 },
cw: { type: 'string', nullable: true, minLength: 1, maxLength: 500 },
localOnly: { type: 'boolean', default: false },
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
@ -301,7 +303,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.renoteId === ps.editId) {
throw new ApiError(meta.errors.cannotQuoteCurrentPost);
}
if (ps.renoteId != null) {
// Fetch renote to note
renote = await this.notesRepository.findOneBy({ id: ps.renoteId });
@ -378,6 +380,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
}
if (ps.lang) {
if (!Object.keys(langmap).includes(ps.lang.toLowerCase())) throw new Error('invalid param');
ps.lang = ps.lang.toLowerCase();
} else {
ps.lang = null;
}
let channel: MiChannel | null = null;
if (ps.channelId != null) {
channel = await this.channelsRepository.findOneBy({ id: ps.channelId, isArchived: false });
@ -396,6 +405,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
} : undefined,
text: ps.text ?? undefined,
lang: ps.lang,
reply,
renote,
cw: ps.cw,

View file

@ -32,6 +32,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
</button>
</template>
<button v-click-anime v-tooltip="i18n.ts.language" :class="['_button', $style.headerRightItem]" @click="setLanguage">
<span><i class="ph-translate ph-bold ph-lg"></i></span>
<span v-if="language !== '' && language != null" :class="$style.headerRightButtonText">{{ language.split("-")[0] }}</span>
</button>
<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly">
<span v-if="!localOnly"><i class="ph-rocket-launch ph-bold ph-lg"></i></span>
<span v-else><i class="ph-rocket ph-bold ph-lg"></i></span>
@ -130,6 +134,8 @@ import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js';
import { langmap } from '@/scripts/langmap.js';
import { MenuItem } from '@/types/menu.js';
const $i = signinRequired();
@ -144,6 +150,7 @@ const props = withDefaults(defineProps<{
initialText?: string;
initialCw?: string;
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
initialLanguage?: (typeof Misskey.languages)[number];
initialFiles?: Misskey.entities.DriveFile[];
initialLocalOnly?: boolean;
initialVisibleUsers?: Misskey.entities.UserDetailed[];
@ -175,6 +182,7 @@ const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
const cwInputEl = shallowRef<HTMLInputElement | null>(null);
const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
const visibilityButton = shallowRef<HTMLElement | null>(null);
const languageButton = shallowRef<HTMLElement | null>(null);
const posting = ref(false);
const posted = ref(false);
@ -202,6 +210,7 @@ const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'
const imeText = ref('');
const showingOptions = ref(false);
const textAreaReadOnly = ref(false);
const language = ref<string | null>(props.initialLanguage ?? defaultStore.state.recentlyUsedPostLanguages[0] ?? localStorage.getItem('lang')?.split('-')[0]);
const draftKey = computed((): string => {
let key = props.channel ? `channel:${props.channel.id}` : '';
@ -362,6 +371,7 @@ function watchForDraft() {
watch(files, () => saveDraft(), { deep: true });
watch(visibility, () => saveDraft());
watch(localOnly, () => saveDraft());
watch(language, () => saveDraft());
}
function MFMWindow() {
@ -535,6 +545,60 @@ async function toggleReactionAcceptance() {
reactionAcceptance.value = select.result;
}
function setLanguage(ev: MouseEvent) {
const actions: Array<MenuItem> = [];
if (language.value != null) actions.push({
text: langmap[language.value].nativeName,
danger: false,
active: true,
action: () => {},
});
const langs = Object.keys(langmap);
// Show recently used language first
let recentlyUsedLanguagesExist = false;
for (const lang of defaultStore.state.recentlyUsedPostLanguages) {
if (lang === language.value) continue;
if (!langs.includes(lang)) continue;
actions.push({
text: langmap[lang].nativeName,
danger: false,
active: false,
action: () => {
language.value = lang;
},
});
recentlyUsedLanguagesExist = true;
}
if (recentlyUsedLanguagesExist) actions.push({ type: 'divider' });
actions.push({
text: i18n.ts.noLanguage,
danger: false,
active: false,
action: () => {
language.value = null;
},
});
for (const lang of langs) {
if (lang === language.value) continue;
if (defaultStore.state.recentlyUsedPostLanguages.includes(lang)) continue;
actions.push({
text: langmap[lang].nativeName,
danger: false,
active: false,
action: () => {
language.value = lang;
},
});
}
os.popupMenu(actions, ev.currentTarget ?? ev.target);
}
function pushVisibleUser(user: Misskey.entities.UserDetailed) {
if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
visibleUsers.value.push(user);
@ -676,6 +740,7 @@ function saveDraft() {
cw: cw.value,
visibility: visibility.value,
localOnly: localOnly.value,
lang: language.value,
files: files.value,
poll: poll.value,
},
@ -776,6 +841,7 @@ async function post(ev?: MouseEvent) {
channelId: props.channel ? props.channel.id : undefined,
poll: poll.value,
cw: useCw.value ? cw.value ?? '' : null,
lang: language.value ? language.value : null,
localOnly: localOnly.value,
visibility: visibility.value,
visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined,
@ -883,6 +949,16 @@ async function post(ev?: MouseEvent) {
text: err.message + '\n' + (err as any).id,
});
});
// update recentlyUsedLanguages
if (language.value != null) {
const languages = Object.keys(langmap);
const maxLength = 6;
defaultStore.set('recentlyUsedPostLanguages', [language.value].concat(defaultStore.state.recentlyUsedPostLanguages.filter((lang) => {
return (lang !== language.value && languages.includes(lang));
})).slice(0, maxLength));
}
}
function cancel() {
@ -985,6 +1061,7 @@ onMounted(() => {
useCw.value = draft.data.useCw;
visibility.value = draft.data.visibility;
localOnly.value = draft.data.localOnly;
language.value = draft.data.lang;
files.value = (draft.data.files || []).filter(draftFile => draftFile);
if (draft.data.poll) {
@ -1010,6 +1087,7 @@ onMounted(() => {
}
visibility.value = init.visibility;
localOnly.value = init.localOnly ?? false;
language.value = init.lang;
quoteId.value = init.renote ? init.renote.id : null;
}

View file

@ -24,6 +24,7 @@ const props = defineProps<{
initialText?: string;
initialCw?: string;
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
initialLanguage?: (typeof Misskey.languages)[number];
initialFiles?: Misskey.entities.DriveFile[];
initialLocalOnly?: boolean;
initialVisibleUsers?: Misskey.entities.UserDetailed[];
@ -31,7 +32,7 @@ const props = defineProps<{
instant?: boolean;
fixed?: boolean;
autofocus?: boolean;
editId?: Misskey.entities.Note["id"];
editId?: Misskey.entities.Note['id'];
}>();
const emit = defineEmits<{

View file

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

View file

@ -529,6 +529,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore,
},
recentlyUsedPostLanguages: {
where: 'account',
default: [] as string[],
},
}));
// TODO: 他のタブと永続化されたstateを同期

View file

@ -338,3 +338,96 @@ export type ModerationLogPayloads = {
fileId: 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

@ -19,6 +19,7 @@ export const mutedNoteReasons = consts.mutedNoteReasons;
export const followingVisibilities = consts.followingVisibilities;
export const followersVisibilities = consts.followersVisibilities;
export const moderationLogTypes = consts.moderationLogTypes;
export const languages = consts.languages;
// api extractor not supported yet
//export * as api from './api.js';