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" withSensitive: "Include notes with sensitive files"
userSaysSomethingSensitive: "Post by {name} contains sensitive content" userSaysSomethingSensitive: "Post by {name} contains sensitive content"
enableHorizontalSwipe: "Swipe to switch tabs" enableHorizontalSwipe: "Swipe to switch tabs"
noLanguage: "No language"
_bubbleGame: _bubbleGame:
howToPlay: "How to play" howToPlay: "How to play"
_howToPlay: _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 { isUserRelated } from '@/misc/is-user-related.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 { langmap } from '@/misc/langmap.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -133,6 +134,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?: MiNote | null; reply?: MiNote | null;
renote?: MiNote | null; renote?: MiNote | null;
files?: MiDriveFile[] | null; files?: MiDriveFile[] | null;
@ -337,6 +339,13 @@ export class NoteCreateService implements OnApplicationShutdown {
data.text = null; 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 tags = data.apHashtags;
let emojis = data.apEmojis; let emojis = data.apEmojis;
let mentionedUsers = data.apMentions; let mentionedUsers = data.apMentions;
@ -579,6 +588,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

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

@ -454,6 +454,9 @@ export class ApRendererService {
mediaType: 'text/x.misskeymarkdown', mediaType: 'text/x.misskeymarkdown',
}, },
}), }),
contentMap: note.lang ? {
[note.lang]: content,
} : null,
_misskey_quote: quote, _misskey_quote: quote,
quoteUrl: quote, quoteUrl: quote,
quoteUri: quote, quoteUri: quote,
@ -746,6 +749,9 @@ export class ApRendererService {
mediaType: 'text/x.misskeymarkdown', mediaType: 'text/x.misskeymarkdown',
}, },
}), }),
contentMap: note.lang ? {
[note.lang]: content,
} : null,
_misskey_quote: quote, _misskey_quote: quote,
quoteUrl: quote, quoteUrl: quote,
quoteUri: quote, quoteUri: quote,

View file

@ -25,6 +25,7 @@ import { StatusError } from '@/misc/status-error.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { checkHttps } from '@/misc/check-https.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 { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
import { ApLoggerService } from '../ApLoggerService.js'; import { ApLoggerService } from '../ApLoggerService.js';
import { ApMfmService } from '../ApMfmService.js'; import { ApMfmService } from '../ApMfmService.js';
@ -244,12 +245,21 @@ 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') { if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
text = note.source.content; 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') { } 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') {
text = this.apMfmService.htmlToMfm(note.content, note.tag); 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 // vote
if (reply && reply.hasPoll) { if (reply && reply.hasPoll) {
const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });
@ -290,6 +300,7 @@ export class ApNoteService {
name: note.name, name: note.name,
cw, cw,
text, text,
lang,
localOnly: false, localOnly: false,
visibility, visibility,
visibleUsers, visibleUsers,
@ -452,12 +463,21 @@ 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') { if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
text = note.source.content; 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') { } 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') {
text = this.apMfmService.htmlToMfm(note.content, note.tag); 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 // vote
if (reply && reply.hasPoll) { if (reply && reply.hasPoll) {
const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });
@ -498,6 +518,7 @@ export class ApNoteService {
name: note.name, name: note.name,
cw, cw,
text, text,
lang,
localOnly: false, localOnly: false,
visibility, visibility,
visibleUsers, visibleUsers,

View file

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

View file

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

View file

@ -4,668 +4,384 @@
*/ */
// TODO: sharedに置いてフロントエンドのと統合したい // TODO: sharedに置いてフロントエンドのと統合したい
export const langmap = { export const iso639Langs1 = {
'ach': { af: {
nativeName: 'Lwo',
},
'ady': {
nativeName: 'Адыгэбзэ',
},
'af': {
nativeName: 'Afrikaans', nativeName: 'Afrikaans',
}, },
'af-NA': { ak: {
nativeName: 'Afrikaans (Namibia)',
},
'af-ZA': {
nativeName: 'Afrikaans (South Africa)',
},
'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': { bn: {
nativeName: 'Български',
},
'bn': {
nativeName: 'বাংলা', nativeName: 'বাংলা',
}, },
'bn-IN': { br: {
nativeName: 'বাংলা (ভারত)',
},
'bn-BD': {
nativeName: 'বাংলা(বাংলাদেশ)',
},
'br': {
nativeName: 'Brezhoneg', nativeName: 'Brezhoneg',
}, },
'bs-BA': { bs: {
nativeName: 'Bosanski', nativeName: 'Bosanski',
}, },
'ca': { ca: {
nativeName: 'Català', nativeName: 'Català',
}, },
'ca-ES': { cs: {
nativeName: 'Català',
},
'cak': {
nativeName: 'Maya Kaqchikel',
},
'ck-US': {
nativeName: 'ᏣᎳᎩ (tsalagi)',
},
'cs': {
nativeName: 'Čeština', nativeName: 'Čeština',
}, },
'cs-CZ': { cy: {
nativeName: 'Čeština',
},
'cy': {
nativeName: 'Cymraeg', nativeName: 'Cymraeg',
}, },
'cy-GB': { da: {
nativeName: 'Cymraeg',
},
'da': {
nativeName: 'Dansk', nativeName: 'Dansk',
}, },
'da-DK': { de: {
nativeName: 'Dansk',
},
'de': {
nativeName: 'Deutsch', nativeName: 'Deutsch',
}, },
'de-AT': { el: {
nativeName: 'Deutsch (Österreich)',
},
'de-DE': {
nativeName: 'Deutsch (Deutschland)',
},
'de-CH': {
nativeName: 'Deutsch (Schweiz)',
},
'dsb': {
nativeName: 'Dolnoserbšćina',
},
'el': {
nativeName: 'Ελληνικά', nativeName: 'Ελληνικά',
}, },
'el-GR': { en: {
nativeName: 'Ελληνικά',
},
'en': {
nativeName: 'English', nativeName: 'English',
}, },
'en-GB': { eo: {
nativeName: 'English (UK)',
},
'en-AU': {
nativeName: 'English (Australia)',
},
'en-CA': {
nativeName: 'English (Canada)',
},
'en-IE': {
nativeName: 'English (Ireland)',
},
'en-IN': {
nativeName: 'English (India)',
},
'en-PI': {
nativeName: 'English (Pirate)',
},
'en-SG': {
nativeName: 'English (Singapore)',
},
'en-UD': {
nativeName: 'English (Upside Down)',
},
'en-US': {
nativeName: 'English (US)',
},
'en-ZA': {
nativeName: 'English (South Africa)',
},
'en@pirate': {
nativeName: 'English (Pirate)',
},
'eo': {
nativeName: 'Esperanto', nativeName: 'Esperanto',
}, },
'eo-EO': { es: {
nativeName: 'Esperanto',
},
'es': {
nativeName: 'Español', nativeName: 'Español',
}, },
'es-AR': { et: {
nativeName: 'Español (Argentine)',
},
'es-419': {
nativeName: 'Español (Latinoamérica)',
},
'es-CL': {
nativeName: 'Español (Chile)',
},
'es-CO': {
nativeName: 'Español (Colombia)',
},
'es-EC': {
nativeName: 'Español (Ecuador)',
},
'es-ES': {
nativeName: 'Español (España)',
},
'es-LA': {
nativeName: 'Español (Latinoamérica)',
},
'es-NI': {
nativeName: 'Español (Nicaragua)',
},
'es-MX': {
nativeName: 'Español (México)',
},
'es-US': {
nativeName: 'Español (Estados Unidos)',
},
'es-VE': {
nativeName: 'Español (Venezuela)',
},
'et': {
nativeName: 'eesti keel', nativeName: 'eesti keel',
}, },
'et-EE': { eu: {
nativeName: 'Eesti (Estonia)',
},
'eu': {
nativeName: 'Euskara', nativeName: 'Euskara',
}, },
'eu-ES': { fa: {
nativeName: 'Euskara',
},
'fa': {
nativeName: 'فارسی', nativeName: 'فارسی',
rtl: true,
}, },
'fa-IR': { ff: {
nativeName: 'فارسی',
},
'fb-LT': {
nativeName: 'Leet Speak',
},
'ff': {
nativeName: 'Fulah', nativeName: 'Fulah',
}, },
'fi': { fi: {
nativeName: 'Suomi', nativeName: 'Suomi',
}, },
'fi-FI': { fo: {
nativeName: 'Suomi',
},
'fo': {
nativeName: 'Føroyskt', nativeName: 'Føroyskt',
}, },
'fo-FO': { fr: {
nativeName: 'Føroyskt (Færeyjar)',
},
'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': { gd: {
nativeName: 'Gaeilge',
},
'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': { he: {
nativeName: 'Ἑλληνική ἀρχαία',
},
'he': {
nativeName: 'עברית‏', nativeName: 'עברית‏',
rtl: true,
}, },
'he-IL': { hi: {
nativeName: 'עברית‏',
},
'hi': {
nativeName: 'हिन्दी', nativeName: 'हिन्दी',
}, },
'hi-IN': { hr: {
nativeName: 'हिन्दी',
},
'hr': {
nativeName: 'Hrvatski', nativeName: 'Hrvatski',
}, },
'hr-HR': { ht: {
nativeName: 'Hrvatski',
},
'hsb': {
nativeName: 'Hornjoserbšćina',
},
'ht': {
nativeName: 'Kreyòl', nativeName: 'Kreyòl',
}, },
'hu': { hu: {
nativeName: 'Magyar', nativeName: 'Magyar',
}, },
'hu-HU': { hy: {
nativeName: 'Magyar',
},
'hy': {
nativeName: 'Հայերեն', nativeName: 'Հայերեն',
}, },
'hy-AM': { id: {
nativeName: 'Հայերեն (Հայաստան)',
},
'id': {
nativeName: 'Bahasa Indonesia', nativeName: 'Bahasa Indonesia',
}, },
'id-ID': { is: {
nativeName: 'Bahasa Indonesia',
},
'is': {
nativeName: 'Íslenska', nativeName: 'Íslenska',
}, },
'is-IS': { it: {
nativeName: 'Íslenska (Iceland)',
},
'it': {
nativeName: 'Italiano', nativeName: 'Italiano',
}, },
'it-IT': { ja: {
nativeName: 'Italiano',
},
'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': { kl: {
nativeName: 'ភាសាខ្មែរ',
},
'kl': {
nativeName: 'kalaallisut', nativeName: 'kalaallisut',
}, },
'km-KH': { km: {
nativeName: 'ភាសាខ្មែរ', nativeName: 'ភាសាខ្មែរ',
}, },
'kab': { kn: {
nativeName: 'Taqbaylit',
},
'kn': {
nativeName: 'ಕನ್ನಡ', nativeName: 'ಕನ್ನಡ',
}, },
'kn-IN': { ko: {
nativeName: 'ಕನ್ನಡ (India)',
},
'ko': {
nativeName: '한국어', nativeName: '한국어',
}, },
'ko-KR': { ku: {
nativeName: '한국어 (한국)',
},
'ku-TR': {
nativeName: 'Kurdî', nativeName: 'Kurdî',
}, },
'kw': { kw: {
nativeName: 'Kernewek', nativeName: 'Kernewek',
}, },
'la': { la: {
nativeName: 'Latin', nativeName: 'Latin',
}, },
'la-VA': { lb: {
nativeName: 'Latin',
},
'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': { lv: {
nativeName: 'Lietuvių',
},
'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': { ml: {
nativeName: 'Македонски (Македонски)',
},
'ml': {
nativeName: 'മലയാളം', nativeName: 'മലയാളം',
}, },
'ml-IN': { mn: {
nativeName: 'മലയാളം',
},
'mn-MN': {
nativeName: 'Монгол', nativeName: 'Монгол',
}, },
'mr': { mr: {
nativeName: 'मराठी', nativeName: 'मराठी',
}, },
'mr-IN': { ms: {
nativeName: 'मराठी',
},
'ms': {
nativeName: 'Bahasa Melayu', nativeName: 'Bahasa Melayu',
}, },
'ms-MY': { mt: {
nativeName: 'Bahasa Melayu',
},
'mt': {
nativeName: 'Malti', nativeName: 'Malti',
}, },
'mt-MT': { my: {
nativeName: 'Malti',
},
'my': {
nativeName: 'ဗမာစကာ', nativeName: 'ဗမာစကာ',
}, },
'no': { no: {
nativeName: 'Norsk', nativeName: 'Norsk',
}, },
'nb': { nb: {
nativeName: 'Norsk (bokmål)', nativeName: 'Norsk (bokmål)',
}, },
'nb-NO': { ne: {
nativeName: 'Norsk (bokmål)',
},
'ne': {
nativeName: 'नेपाली', nativeName: 'नेपाली',
}, },
'ne-NP': { nl: {
nativeName: 'नेपाली',
},
'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': { pl: {
nativeName: 'ਪੰਜਾਬੀ (ਭਾਰਤ ਨੂੰ)',
},
'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': { ru: {
nativeName: 'Română',
},
'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': { sl: {
nativeName: 'Slovenčina (Slovakia)',
},
'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': { sr: {
nativeName: 'Shqip',
},
'sr': {
nativeName: 'Српски', nativeName: 'Српски',
}, },
'sr-RS': { su: {
nativeName: 'Српски (Serbia)',
},
'su': {
nativeName: 'Basa Sunda', nativeName: 'Basa Sunda',
}, },
'sv': { sv: {
nativeName: 'Svenska', nativeName: 'Svenska',
}, },
'sv-SE': { sw: {
nativeName: 'Svenska',
},
'sw': {
nativeName: 'Kiswahili', nativeName: 'Kiswahili',
}, },
'sw-KE': { ta: {
nativeName: 'Kiswahili',
},
'ta': {
nativeName: 'தமிழ்', nativeName: 'தமிழ்',
}, },
'ta-IN': { te: {
nativeName: 'தமிழ்',
},
'te': {
nativeName: 'తెలుగు', nativeName: 'తెలుగు',
}, },
'te-IN': { tg: {
nativeName: 'తెలుగు',
},
'tg': {
nativeName: 'забо́ни тоҷикӣ́', nativeName: 'забо́ни тоҷикӣ́',
}, },
'tg-TJ': { th: {
nativeName: 'тоҷикӣ',
},
'th': {
nativeName: 'ภาษาไทย', nativeName: 'ภาษาไทย',
}, },
'th-TH': { tr: {
nativeName: 'ภาษาไทย (ประเทศไทย)',
},
'fil': {
nativeName: 'Filipino',
},
'tlh': {
nativeName: 'tlhIngan-Hol',
},
'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': { ur: {
nativeName: 'Українська',
},
'ur': {
nativeName: 'اردو', nativeName: 'اردو',
rtl: true,
}, },
'ur-PK': { uz: {
nativeName: 'اردو',
},
'uz': {
nativeName: 'O\'zbek', nativeName: 'O\'zbek',
}, },
'uz-UZ': { vi: {
nativeName: 'O\'zbek',
},
'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': { zh: {
nativeName: 'ייִדיש (German)',
},
'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

@ -61,6 +61,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

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

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 { langmap } 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(langmap)],
nullable: true,
},
cw: { cw: {
type: 'string', type: 'string',
optional: true, nullable: true, 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 { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { langmap } from '@/misc/langmap.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
@ -136,6 +137,7 @@ export const paramDef = {
visibleUserIds: { type: 'array', uniqueItems: true, items: { visibleUserIds: { type: 'array', uniqueItems: true, items: {
type: 'string', format: 'misskey:id', 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 }, cw: { type: 'string', nullable: true, minLength: 1, maxLength: 500 },
localOnly: { type: 'boolean', default: false }, localOnly: { type: 'boolean', default: false },
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null }, 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, 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

@ -12,6 +12,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { NoteEditService } from '@/core/NoteEditService.js'; import { NoteEditService } from '@/core/NoteEditService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { langmap } from '@/misc/langmap.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
@ -164,6 +165,7 @@ export const paramDef = {
format: 'misskey:id', format: 'misskey:id',
}, },
}, },
lang: { type: 'string', enum: Object.keys(langmap), nullable: true, maxLength: 10 },
cw: { type: 'string', nullable: true, minLength: 1, maxLength: 500 }, cw: { type: 'string', nullable: true, minLength: 1, maxLength: 500 },
localOnly: { type: 'boolean', default: false }, localOnly: { type: 'boolean', default: false },
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null }, reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
@ -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; let channel: MiChannel | null = null;
if (ps.channelId != null) { if (ps.channelId != null) {
channel = await this.channelsRepository.findOneBy({ id: ps.channelId, isArchived: false }); 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, 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

@ -32,6 +32,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<span :class="$style.headerRightButtonText">{{ channel.name }}</span> <span :class="$style.headerRightButtonText">{{ channel.name }}</span>
</button> </button>
</template> </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"> <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-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> <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 { claimAchievement } from '@/scripts/achievements.js';
import { emojiPicker } from '@/scripts/emoji-picker.js'; import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mfmFunctionPicker } from '@/scripts/mfm-function-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(); const $i = signinRequired();
@ -144,6 +150,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)[number];
initialFiles?: Misskey.entities.DriveFile[]; initialFiles?: Misskey.entities.DriveFile[];
initialLocalOnly?: boolean; initialLocalOnly?: boolean;
initialVisibleUsers?: Misskey.entities.UserDetailed[]; initialVisibleUsers?: Misskey.entities.UserDetailed[];
@ -175,6 +182,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 | null>(null); const visibilityButton = shallowRef<HTMLElement | null>(null);
const languageButton = shallowRef<HTMLElement | null>(null);
const posting = ref(false); const posting = ref(false);
const posted = ref(false); const posted = ref(false);
@ -202,6 +210,7 @@ const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'
const imeText = ref(''); const imeText = ref('');
const showingOptions = ref(false); const showingOptions = ref(false);
const textAreaReadOnly = 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 => { const draftKey = computed((): string => {
let key = props.channel ? `channel:${props.channel.id}` : ''; let key = props.channel ? `channel:${props.channel.id}` : '';
@ -362,6 +371,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 MFMWindow() { function MFMWindow() {
@ -535,6 +545,60 @@ async function toggleReactionAcceptance() {
reactionAcceptance.value = select.result; 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) { 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);
@ -676,6 +740,7 @@ function saveDraft() {
cw: cw.value, cw: cw.value,
visibility: visibility.value, visibility: visibility.value,
localOnly: localOnly.value, localOnly: localOnly.value,
lang: language.value,
files: files.value, files: files.value,
poll: poll.value, poll: poll.value,
}, },
@ -776,6 +841,7 @@ async function post(ev?: MouseEvent) {
channelId: props.channel ? props.channel.id : undefined, channelId: props.channel ? props.channel.id : undefined,
poll: poll.value, poll: poll.value,
cw: useCw.value ? cw.value ?? '' : null, cw: useCw.value ? cw.value ?? '' : null,
lang: language.value ? language.value : null,
localOnly: localOnly.value, localOnly: localOnly.value,
visibility: visibility.value, visibility: visibility.value,
visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined, 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, 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() { function cancel() {
@ -985,6 +1061,7 @@ onMounted(() => {
useCw.value = draft.data.useCw; useCw.value = draft.data.useCw;
visibility.value = draft.data.visibility; visibility.value = draft.data.visibility;
localOnly.value = draft.data.localOnly; localOnly.value = draft.data.localOnly;
language.value = draft.data.lang;
files.value = (draft.data.files || []).filter(draftFile => draftFile); files.value = (draft.data.files || []).filter(draftFile => draftFile);
if (draft.data.poll) { if (draft.data.poll) {
@ -1010,6 +1087,7 @@ onMounted(() => {
} }
visibility.value = init.visibility; visibility.value = init.visibility;
localOnly.value = init.localOnly ?? false; localOnly.value = init.localOnly ?? false;
language.value = init.lang;
quoteId.value = init.renote ? init.renote.id : null; quoteId.value = init.renote ? init.renote.id : null;
} }

View file

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

View file

@ -4,668 +4,384 @@
*/ */
// TODO: sharedに置いてバックエンドのと統合したい // TODO: sharedに置いてバックエンドのと統合したい
export const langmap = { export const iso639Langs1 = {
'ach': { af: {
nativeName: 'Lwo',
},
'ady': {
nativeName: 'Адыгэбзэ',
},
'af': {
nativeName: 'Afrikaans', nativeName: 'Afrikaans',
}, },
'af-NA': { ak: {
nativeName: 'Afrikaans (Namibia)',
},
'af-ZA': {
nativeName: 'Afrikaans (South Africa)',
},
'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': { bn: {
nativeName: 'Български',
},
'bn': {
nativeName: 'বাংলা', nativeName: 'বাংলা',
}, },
'bn-IN': { br: {
nativeName: 'বাংলা (ভারত)',
},
'bn-BD': {
nativeName: 'বাংলা(বাংলাদেশ)',
},
'br': {
nativeName: 'Brezhoneg', nativeName: 'Brezhoneg',
}, },
'bs-BA': { bs: {
nativeName: 'Bosanski', nativeName: 'Bosanski',
}, },
'ca': { ca: {
nativeName: 'Català', nativeName: 'Català',
}, },
'ca-ES': { cs: {
nativeName: 'Català',
},
'cak': {
nativeName: 'Maya Kaqchikel',
},
'ck-US': {
nativeName: 'ᏣᎳᎩ (tsalagi)',
},
'cs': {
nativeName: 'Čeština', nativeName: 'Čeština',
}, },
'cs-CZ': { cy: {
nativeName: 'Čeština',
},
'cy': {
nativeName: 'Cymraeg', nativeName: 'Cymraeg',
}, },
'cy-GB': { da: {
nativeName: 'Cymraeg',
},
'da': {
nativeName: 'Dansk', nativeName: 'Dansk',
}, },
'da-DK': { de: {
nativeName: 'Dansk',
},
'de': {
nativeName: 'Deutsch', nativeName: 'Deutsch',
}, },
'de-AT': { el: {
nativeName: 'Deutsch (Österreich)',
},
'de-DE': {
nativeName: 'Deutsch (Deutschland)',
},
'de-CH': {
nativeName: 'Deutsch (Schweiz)',
},
'dsb': {
nativeName: 'Dolnoserbšćina',
},
'el': {
nativeName: 'Ελληνικά', nativeName: 'Ελληνικά',
}, },
'el-GR': { en: {
nativeName: 'Ελληνικά',
},
'en': {
nativeName: 'English', nativeName: 'English',
}, },
'en-GB': { eo: {
nativeName: 'English (UK)',
},
'en-AU': {
nativeName: 'English (Australia)',
},
'en-CA': {
nativeName: 'English (Canada)',
},
'en-IE': {
nativeName: 'English (Ireland)',
},
'en-IN': {
nativeName: 'English (India)',
},
'en-PI': {
nativeName: 'English (Pirate)',
},
'en-SG': {
nativeName: 'English (Singapore)',
},
'en-UD': {
nativeName: 'English (Upside Down)',
},
'en-US': {
nativeName: 'English (US)',
},
'en-ZA': {
nativeName: 'English (South Africa)',
},
'en@pirate': {
nativeName: 'English (Pirate)',
},
'eo': {
nativeName: 'Esperanto', nativeName: 'Esperanto',
}, },
'eo-EO': { es: {
nativeName: 'Esperanto',
},
'es': {
nativeName: 'Español', nativeName: 'Español',
}, },
'es-AR': { et: {
nativeName: 'Español (Argentine)',
},
'es-419': {
nativeName: 'Español (Latinoamérica)',
},
'es-CL': {
nativeName: 'Español (Chile)',
},
'es-CO': {
nativeName: 'Español (Colombia)',
},
'es-EC': {
nativeName: 'Español (Ecuador)',
},
'es-ES': {
nativeName: 'Español (España)',
},
'es-LA': {
nativeName: 'Español (Latinoamérica)',
},
'es-NI': {
nativeName: 'Español (Nicaragua)',
},
'es-MX': {
nativeName: 'Español (México)',
},
'es-US': {
nativeName: 'Español (Estados Unidos)',
},
'es-VE': {
nativeName: 'Español (Venezuela)',
},
'et': {
nativeName: 'eesti keel', nativeName: 'eesti keel',
}, },
'et-EE': { eu: {
nativeName: 'Eesti (Estonia)',
},
'eu': {
nativeName: 'Euskara', nativeName: 'Euskara',
}, },
'eu-ES': { fa: {
nativeName: 'Euskara',
},
'fa': {
nativeName: 'فارسی', nativeName: 'فارسی',
rtl: true,
}, },
'fa-IR': { ff: {
nativeName: 'فارسی',
},
'fb-LT': {
nativeName: 'Leet Speak',
},
'ff': {
nativeName: 'Fulah', nativeName: 'Fulah',
}, },
'fi': { fi: {
nativeName: 'Suomi', nativeName: 'Suomi',
}, },
'fi-FI': { fo: {
nativeName: 'Suomi',
},
'fo': {
nativeName: 'Føroyskt', nativeName: 'Føroyskt',
}, },
'fo-FO': { fr: {
nativeName: 'Føroyskt (Færeyjar)',
},
'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': { gd: {
nativeName: 'Gaeilge',
},
'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': { he: {
nativeName: 'Ἑλληνική ἀρχαία',
},
'he': {
nativeName: 'עברית‏', nativeName: 'עברית‏',
rtl: true,
}, },
'he-IL': { hi: {
nativeName: 'עברית‏',
},
'hi': {
nativeName: 'हिन्दी', nativeName: 'हिन्दी',
}, },
'hi-IN': { hr: {
nativeName: 'हिन्दी',
},
'hr': {
nativeName: 'Hrvatski', nativeName: 'Hrvatski',
}, },
'hr-HR': { ht: {
nativeName: 'Hrvatski',
},
'hsb': {
nativeName: 'Hornjoserbšćina',
},
'ht': {
nativeName: 'Kreyòl', nativeName: 'Kreyòl',
}, },
'hu': { hu: {
nativeName: 'Magyar', nativeName: 'Magyar',
}, },
'hu-HU': { hy: {
nativeName: 'Magyar',
},
'hy': {
nativeName: 'Հայերեն', nativeName: 'Հայերեն',
}, },
'hy-AM': { id: {
nativeName: 'Հայերեն (Հայաստան)',
},
'id': {
nativeName: 'Bahasa Indonesia', nativeName: 'Bahasa Indonesia',
}, },
'id-ID': { is: {
nativeName: 'Bahasa Indonesia',
},
'is': {
nativeName: 'Íslenska', nativeName: 'Íslenska',
}, },
'is-IS': { it: {
nativeName: 'Íslenska (Iceland)',
},
'it': {
nativeName: 'Italiano', nativeName: 'Italiano',
}, },
'it-IT': { ja: {
nativeName: 'Italiano',
},
'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': { kl: {
nativeName: 'ភាសាខ្មែរ',
},
'kl': {
nativeName: 'kalaallisut', nativeName: 'kalaallisut',
}, },
'km-KH': { km: {
nativeName: 'ភាសាខ្មែរ', nativeName: 'ភាសាខ្មែរ',
}, },
'kab': { kn: {
nativeName: 'Taqbaylit',
},
'kn': {
nativeName: 'ಕನ್ನಡ', nativeName: 'ಕನ್ನಡ',
}, },
'kn-IN': { ko: {
nativeName: 'ಕನ್ನಡ (India)',
},
'ko': {
nativeName: '한국어', nativeName: '한국어',
}, },
'ko-KR': { ku: {
nativeName: '한국어 (한국)',
},
'ku-TR': {
nativeName: 'Kurdî', nativeName: 'Kurdî',
}, },
'kw': { kw: {
nativeName: 'Kernewek', nativeName: 'Kernewek',
}, },
'la': { la: {
nativeName: 'Latin', nativeName: 'Latin',
}, },
'la-VA': { lb: {
nativeName: 'Latin',
},
'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': { lv: {
nativeName: 'Lietuvių',
},
'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': { ml: {
nativeName: 'Македонски (Македонски)',
},
'ml': {
nativeName: 'മലയാളം', nativeName: 'മലയാളം',
}, },
'ml-IN': { mn: {
nativeName: 'മലയാളം',
},
'mn-MN': {
nativeName: 'Монгол', nativeName: 'Монгол',
}, },
'mr': { mr: {
nativeName: 'मराठी', nativeName: 'मराठी',
}, },
'mr-IN': { ms: {
nativeName: 'मराठी',
},
'ms': {
nativeName: 'Bahasa Melayu', nativeName: 'Bahasa Melayu',
}, },
'ms-MY': { mt: {
nativeName: 'Bahasa Melayu',
},
'mt': {
nativeName: 'Malti', nativeName: 'Malti',
}, },
'mt-MT': { my: {
nativeName: 'Malti',
},
'my': {
nativeName: 'ဗမာစကာ', nativeName: 'ဗမာစကာ',
}, },
'no': { no: {
nativeName: 'Norsk', nativeName: 'Norsk',
}, },
'nb': { nb: {
nativeName: 'Norsk (bokmål)', nativeName: 'Norsk (bokmål)',
}, },
'nb-NO': { ne: {
nativeName: 'Norsk (bokmål)',
},
'ne': {
nativeName: 'नेपाली', nativeName: 'नेपाली',
}, },
'ne-NP': { nl: {
nativeName: 'नेपाली',
},
'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': { pl: {
nativeName: 'ਪੰਜਾਬੀ (ਭਾਰਤ ਨੂੰ)',
},
'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': { ru: {
nativeName: 'Română',
},
'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': { sl: {
nativeName: 'Slovenčina (Slovakia)',
},
'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': { sr: {
nativeName: 'Shqip',
},
'sr': {
nativeName: 'Српски', nativeName: 'Српски',
}, },
'sr-RS': { su: {
nativeName: 'Српски (Serbia)',
},
'su': {
nativeName: 'Basa Sunda', nativeName: 'Basa Sunda',
}, },
'sv': { sv: {
nativeName: 'Svenska', nativeName: 'Svenska',
}, },
'sv-SE': { sw: {
nativeName: 'Svenska',
},
'sw': {
nativeName: 'Kiswahili', nativeName: 'Kiswahili',
}, },
'sw-KE': { ta: {
nativeName: 'Kiswahili',
},
'ta': {
nativeName: 'தமிழ்', nativeName: 'தமிழ்',
}, },
'ta-IN': { te: {
nativeName: 'தமிழ்',
},
'te': {
nativeName: 'తెలుగు', nativeName: 'తెలుగు',
}, },
'te-IN': { tg: {
nativeName: 'తెలుగు',
},
'tg': {
nativeName: 'забо́ни тоҷикӣ́', nativeName: 'забо́ни тоҷикӣ́',
}, },
'tg-TJ': { th: {
nativeName: 'тоҷикӣ',
},
'th': {
nativeName: 'ภาษาไทย', nativeName: 'ภาษาไทย',
}, },
'th-TH': { tr: {
nativeName: 'ภาษาไทย (ประเทศไทย)',
},
'fil': {
nativeName: 'Filipino',
},
'tlh': {
nativeName: 'tlhIngan-Hol',
},
'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': { ur: {
nativeName: 'Українська',
},
'ur': {
nativeName: 'اردو', nativeName: 'اردو',
rtl: true,
}, },
'ur-PK': { uz: {
nativeName: 'اردو',
},
'uz': {
nativeName: 'O\'zbek', nativeName: 'O\'zbek',
}, },
'uz-UZ': { vi: {
nativeName: 'O\'zbek',
},
'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': { zh: {
nativeName: 'ייִדיש (German)',
},
'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

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

View file

@ -338,3 +338,96 @@ export type ModerationLogPayloads = {
fileId: string; 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 followingVisibilities = consts.followingVisibilities;
export const followersVisibilities = consts.followersVisibilities; export const followersVisibilities = consts.followersVisibilities;
export const moderationLogTypes = consts.moderationLogTypes; export const moderationLogTypes = consts.moderationLogTypes;
export const languages = consts.languages;
// api extractor not supported yet // api extractor not supported yet
//export * as api from './api.js'; //export * as api from './api.js';