From 08a2162aa71cf9369d2797c3a19f1ff41b950d32 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Sat, 23 May 2020 13:19:31 +0900 Subject: [PATCH] =?UTF-8?q?feat(client):=20=E7=BF=BB=E8=A8=B3=E3=82=92Inde?= =?UTF-8?q?xedDB=E3=81=AB=E4=BF=9D=E5=AD=98=E3=83=BB=E3=83=97=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E9=80=9A=E7=9F=A5=E3=82=92=E7=BF=BB=E8=A8=B3?= =?UTF-8?q?=20(#6396)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * tabun ok * better msg * oops * fix lint * Update gulpfile.ts Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * Update src/client/scripts/set-i18n-contexts.ts Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * refactor Co-authored-by: acid-chicken <root@acid-chicken.com> * ✨ * wip * fix lint * たぶんおk * fix flush * Translate Notification * remove console.log * fix * add notifications * remove san * wip * ok * :v: * Update src/prelude/array.ts Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * wip * i18n refactor * Update init.ts * :v: Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> Co-authored-by: syuilo <syuilotan@yahoo.co.jp> --- gulpfile.ts | 6 +- locales/ja-JP.yml | 16 +++ package.json | 2 + src/client/app.vue | 3 - src/client/components/captcha.vue | 2 - src/client/components/cw-button.vue | 3 - src/client/components/date-separated-list.vue | 3 - src/client/components/dialog.vue | 3 - src/client/components/drive-window.vue | 3 - src/client/components/drive.file.vue | 3 - src/client/components/drive.folder.vue | 3 - src/client/components/drive.nav-folder.vue | 3 - src/client/components/drive.vue | 3 - src/client/components/emoji-picker.vue | 3 - src/client/components/error.vue | 2 - src/client/components/follow-button.vue | 3 - src/client/components/google.vue | 2 - src/client/components/image-viewer.vue | 3 - src/client/components/instance-stats.vue | 3 - src/client/components/media-banner.vue | 2 - src/client/components/media-image.vue | 2 - src/client/components/media-video.vue | 2 - src/client/components/mention.vue | 2 - src/client/components/note.vue | 2 - src/client/components/notes.vue | 3 - src/client/components/notification.vue | 6 +- src/client/components/notifications.vue | 3 - src/client/components/page/page.post.vue | 2 - src/client/components/page/page.vue | 3 - src/client/components/poll-editor.vue | 2 - src/client/components/poll.vue | 2 - src/client/components/post-form-attaches.vue | 3 - src/client/components/post-form.vue | 3 - src/client/components/reaction-icon.vue | 2 - src/client/components/reaction-picker.vue | 3 - .../components/reactions-viewer.details.vue | 2 - src/client/components/remote-caution.vue | 2 - src/client/components/signin-dialog.vue | 3 - src/client/components/signin.vue | 3 - src/client/components/signup-dialog.vue | 3 - src/client/components/signup.vue | 3 - src/client/components/stream-indicator.vue | 2 - src/client/components/sub-note-content.vue | 2 - src/client/components/time.vue | 2 - src/client/components/uploader.vue | 2 - src/client/components/url-preview-popup.vue | 3 - src/client/components/url-preview.vue | 3 - src/client/components/user-list.vue | 3 - src/client/components/user-menu.vue | 3 - src/client/components/user-preview.vue | 3 - src/client/components/user-select.vue | 3 - src/client/components/users-dialog.vue | 3 - src/client/components/visibility-chooser.vue | 2 - src/client/components/window.vue | 3 - src/client/config.ts | 5 +- src/client/db.ts | 68 +++++++++++++ src/client/i18n.ts | 12 --- src/client/init.ts | 44 ++++----- src/client/pages/about-misskey.vue | 3 - src/client/pages/about.vue | 3 - src/client/pages/announcements.vue | 3 - src/client/pages/auth.form.vue | 2 - src/client/pages/auth.vue | 2 - src/client/pages/doc.vue | 3 - src/client/pages/explore.vue | 3 - src/client/pages/follow.vue | 3 - src/client/pages/index.welcome.entrance.vue | 3 - src/client/pages/index.welcome.setup.vue | 2 - src/client/pages/instance/announcements.vue | 3 - .../pages/instance/federation.instance.vue | 3 - src/client/pages/instance/federation.vue | 3 - src/client/pages/instance/index.vue | 3 - src/client/pages/instance/queue.queue.vue | 3 - src/client/pages/instance/queue.vue | 3 - src/client/pages/instance/relays.vue | 3 - src/client/pages/instance/settings.vue | 3 - src/client/pages/instance/users.user.vue | 3 - src/client/pages/messaging/index.vue | 3 - .../pages/messaging/messaging-room.form.vue | 2 - .../messaging/messaging-room.message.vue | 2 - src/client/pages/messaging/messaging-room.vue | 3 - src/client/pages/miauth.vue | 2 - .../pages/my-antennas/index.antenna.vue | 3 - src/client/pages/my-groups/group.vue | 3 - src/client/pages/my-lists/list.vue | 3 - src/client/pages/my-settings/2fa.vue | 2 - src/client/pages/my-settings/api.vue | 2 - src/client/pages/my-settings/drive.vue | 3 - .../pages/my-settings/import-export.vue | 3 - src/client/pages/my-settings/integration.vue | 3 - src/client/pages/my-settings/mute-block.vue | 3 - src/client/pages/my-settings/privacy.vue | 3 - src/client/pages/my-settings/profile.vue | 3 - src/client/pages/my-settings/reaction.vue | 3 - src/client/pages/my-settings/security.vue | 3 - src/client/pages/not-found.vue | 3 - src/client/pages/note.vue | 2 - .../page-editor/els/page-editor.el.button.vue | 3 - .../page-editor/els/page-editor.el.canvas.vue | 3 - .../els/page-editor.el.counter.vue | 3 - .../page-editor/els/page-editor.el.if.vue | 3 - .../page-editor/els/page-editor.el.image.vue | 3 - .../els/page-editor.el.number-input.vue | 3 - .../page-editor/els/page-editor.el.post.vue | 3 - .../els/page-editor.el.radio-button.vue | 2 - .../els/page-editor.el.section.vue | 3 - .../page-editor/els/page-editor.el.switch.vue | 3 - .../els/page-editor.el.text-input.vue | 3 - .../page-editor/els/page-editor.el.text.vue | 3 - .../els/page-editor.el.textarea-input.vue | 3 - .../els/page-editor.el.textarea.vue | 3 - .../page-editor/page-editor.container.vue | 3 - .../page-editor/page-editor.script-block.vue | 3 - src/client/pages/page-editor/page-editor.vue | 3 - src/client/pages/pages.vue | 2 - src/client/pages/preferences/index.vue | 22 ++++- src/client/pages/preferences/sidebar.vue | 3 - src/client/pages/preferences/theme.vue | 3 - src/client/pages/room/room.vue | 3 - src/client/pages/scratchpad.vue | 3 - src/client/pages/share.vue | 3 - src/client/pages/user/follow-list.vue | 3 - src/client/pages/user/index.photos.vue | 2 - src/client/scripts/compose-notification.ts | 97 +++++++++++++------ src/client/scripts/set-i18n-contexts.ts | 18 ++++ src/client/{sw.js => sw.ts} | 13 +-- src/client/tsconfig.json | 5 + src/client/widgets/activity.chart.vue | 2 - src/client/widgets/activity.vue | 2 - src/client/widgets/calendar.vue | 2 - src/client/widgets/memo.vue | 2 - src/client/widgets/notifications.vue | 2 - src/client/widgets/photos.vue | 2 - src/client/widgets/rss.vue | 2 - src/client/widgets/timeline.vue | 2 - src/client/widgets/trends.vue | 2 - src/misc/get-note-summary.ts | 14 +-- src/prelude/array.ts | 12 ++- src/server/web/index.ts | 3 +- src/server/web/views/flush.pug | 32 ++++-- src/services/push-notification.ts | 7 +- webpack.config.ts | 2 +- yarn.lock | 10 ++ 143 files changed, 290 insertions(+), 433 deletions(-) create mode 100644 src/client/db.ts delete mode 100644 src/client/i18n.ts create mode 100644 src/client/scripts/set-i18n-contexts.ts rename src/client/{sw.js => sw.ts} (88%) diff --git a/gulpfile.ts b/gulpfile.ts index 262c0a503..880adb51d 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -11,7 +11,7 @@ const cleanCSS = require('gulp-clean-css'); const sass = require('gulp-dart-sass'); const fiber = require('fibers'); -const locales = require('./locales'); +const locales: { [x: string]: any } = require('./locales'); const meta = require('./package.json'); gulp.task('build:ts', () => { @@ -31,8 +31,10 @@ gulp.task('build:copy:views', () => gulp.task('build:copy:locales', cb => { fs.mkdirSync('./built/client/assets/locales', { recursive: true }); + const v = { '_version_': meta.version }; + for (const [lang, locale] of Object.entries(locales)) { - fs.writeFileSync(`./built/client/assets/locales/${lang}.${meta.version}.json`, JSON.stringify(locale), 'utf-8'); + fs.writeFileSync(`./built/client/assets/locales/${lang}.${meta.version}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8'); } cb(); diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index ab293eb89..3c7dc6640 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -507,6 +507,8 @@ addRelay: "リレーの追加" inboxUrl: "inboxのURL" addedRelays: "追加済みのリレー" serviceworkerInfo: "プッシュ通知を行うには有効する必要があります。" +deletedNote: "削除された投稿" +invisibleNote: "非公開の投稿" _theme: explore: "テーマを探す" @@ -1102,3 +1104,17 @@ _relayStatus: requesting: "承認待ち" accepted: "承認済み" rejected: "拒否済み" + +_notification: + fileUploaded: "ファイルがアップロードされました" + youGotMention: "{name}からのメンション" + youGotReply: "{name}からのリプライ" + youGotQuote: "{name}による引用" + youRenoted: "{name}がRenoteしました" + youGotPoll: "{name}が投票しました" + youGotMessagingMessageFromUser: "{name}からのチャットがあります" + youGotMessagingMessageFromGroup: "{name}のチャットがあります" + youWereFollowed: "フォローされました" + youReceivedFollowRequest: "フォローリクエストが来ました" + yourFollowRequestAccepted: "フォローリクエストが承認されました" + youWereInvitedToGroup: "グループに招待されました" diff --git a/package.json b/package.json index 15c1478db..d34394d13 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "css-loader": "3.5.3", "cssnano": "4.1.10", "dateformat": "3.0.3", + "deep-entries": "3.1.0", "diskusage": "1.1.3", "double-ended-queue": "2.1.0-0", "escape-regexp": "0.0.1", @@ -151,6 +152,7 @@ "http-proxy-agent": "4.0.1", "http-signature": "1.3.4", "https-proxy-agent": "5.0.0", + "idb-keyval": "3.2.0", "insert-text-at-cursor": "0.3.0", "is-root": "2.1.0", "is-svg": "4.2.1", diff --git a/src/client/app.vue b/src/client/app.vue index 5e7396205..8e192d463 100644 --- a/src/client/app.vue +++ b/src/client/app.vue @@ -136,15 +136,12 @@ import { faGripVertical, faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, import { faBell, faEnvelope, faLaugh, faComments } from '@fortawesome/free-regular-svg-icons'; import { ResizeObserver } from '@juggle/resize-observer'; import { v4 as uuid } from 'uuid'; -import i18n from './i18n'; import { host, instanceName } from './config'; import { search } from './scripts/search'; const DESKTOP_THRESHOLD = 1100; export default Vue.extend({ - i18n, - components: { XClock: () => import('./components/header-clock.vue').then(m => m.default), MkButton: () => import('./components/ui/button.vue').then(m => m.default), diff --git a/src/client/components/captcha.vue b/src/client/components/captcha.vue index 6b1ee6f0b..1a894d935 100644 --- a/src/client/components/captcha.vue +++ b/src/client/components/captcha.vue @@ -7,7 +7,6 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; type Captcha = { render(container: string | Node, options: { @@ -31,7 +30,6 @@ declare global { } export default Vue.extend({ - i18n, props: { provider: { type: String, diff --git a/src/client/components/cw-button.vue b/src/client/components/cw-button.vue index 4516e5210..07a44d970 100644 --- a/src/client/components/cw-button.vue +++ b/src/client/components/cw-button.vue @@ -7,13 +7,10 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import { length } from 'stringz'; import { concat } from '../../prelude/array'; export default Vue.extend({ - i18n, - props: { value: { type: Boolean, diff --git a/src/client/components/date-separated-list.vue b/src/client/components/date-separated-list.vue index b80c6494e..a27e9a05a 100644 --- a/src/client/components/date-separated-list.vue +++ b/src/client/components/date-separated-list.vue @@ -15,11 +15,8 @@ <script lang="ts"> import Vue from 'vue'; import { faAngleUp, faAngleDown } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, - props: { items: { type: Array, diff --git a/src/client/components/dialog.vue b/src/client/components/dialog.vue index da8e54684..58115b47a 100644 --- a/src/client/components/dialog.vue +++ b/src/client/components/dialog.vue @@ -57,11 +57,8 @@ import MkInput from './ui/input.vue'; import MkSelect from './ui/select.vue'; import MkSignin from './signin.vue'; import parseAcct from '../../misc/acct/parse'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, - components: { MkButton, MkInput, diff --git a/src/client/components/drive-window.vue b/src/client/components/drive-window.vue index d63881c0e..c42cb6661 100644 --- a/src/client/components/drive-window.vue +++ b/src/client/components/drive-window.vue @@ -12,13 +12,10 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import XDrive from './drive.vue'; import XWindow from './window.vue'; export default Vue.extend({ - i18n, - components: { XDrive, XWindow, diff --git a/src/client/components/drive.file.vue b/src/client/components/drive.file.vue index a547abf9a..1b24c61df 100644 --- a/src/client/components/drive.file.vue +++ b/src/client/components/drive.file.vue @@ -32,7 +32,6 @@ <script lang="ts"> import Vue from 'vue'; import { faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../i18n'; import copyToClipboard from '../scripts/copy-to-clipboard'; //import updateAvatar from '../api/update-avatar'; //import updateBanner from '../api/update-banner'; @@ -40,8 +39,6 @@ import XFileThumbnail from './drive-file-thumbnail.vue'; import { faDownload, faLink, faICursor, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; export default Vue.extend({ - i18n, - components: { XFileThumbnail }, diff --git a/src/client/components/drive.folder.vue b/src/client/components/drive.folder.vue index b778acc77..9e8065319 100644 --- a/src/client/components/drive.folder.vue +++ b/src/client/components/drive.folder.vue @@ -28,11 +28,8 @@ <script lang="ts"> import Vue from 'vue'; import { faFolder, faFolderOpen } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, - props: { folder: { type: Object, diff --git a/src/client/components/drive.nav-folder.vue b/src/client/components/drive.nav-folder.vue index 0689faecd..9e805a5e9 100644 --- a/src/client/components/drive.nav-folder.vue +++ b/src/client/components/drive.nav-folder.vue @@ -15,11 +15,8 @@ <script lang="ts"> import Vue from 'vue'; import { faCloud } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, - props: { folder: { type: Object, diff --git a/src/client/components/drive.vue b/src/client/components/drive.vue index 08c7097a8..65eb1cb81 100644 --- a/src/client/components/drive.vue +++ b/src/client/components/drive.vue @@ -48,7 +48,6 @@ <script lang="ts"> import Vue from 'vue'; import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; import XNavFolder from './drive.nav-folder.vue'; import XFolder from './drive.folder.vue'; import XFile from './drive.file.vue'; @@ -56,8 +55,6 @@ import XUploader from './uploader.vue'; import MkButton from './ui/button.vue'; export default Vue.extend({ - i18n, - components: { XNavFolder, XFolder, diff --git a/src/client/components/emoji-picker.vue b/src/client/components/emoji-picker.vue index 868a6125c..7871b438c 100644 --- a/src/client/components/emoji-picker.vue +++ b/src/client/components/emoji-picker.vue @@ -64,7 +64,6 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import { emojilist } from '../../misc/emojilist'; import { getStaticImageUrl } from '../scripts/get-static-image-url'; import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory, faUser } from '@fortawesome/free-solid-svg-icons'; @@ -73,8 +72,6 @@ import { groupByX } from '../../prelude/array'; import XPopup from './popup.vue'; export default Vue.extend({ - i18n, - components: { XPopup, }, diff --git a/src/client/components/error.vue b/src/client/components/error.vue index dd9de43c1..fea81305e 100644 --- a/src/client/components/error.vue +++ b/src/client/components/error.vue @@ -9,11 +9,9 @@ <script lang="ts"> import Vue from 'vue'; import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; import MkButton from './ui/button.vue'; export default Vue.extend({ - i18n, components: { MkButton, }, diff --git a/src/client/components/follow-button.vue b/src/client/components/follow-button.vue index 23cb0cd94..7967c0e15 100644 --- a/src/client/components/follow-button.vue +++ b/src/client/components/follow-button.vue @@ -30,12 +30,9 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import { faSpinner, faPlus, faMinus, faHourglassHalf } from '@fortawesome/free-solid-svg-icons'; export default Vue.extend({ - i18n, - props: { user: { type: Object, diff --git a/src/client/components/google.vue b/src/client/components/google.vue index 01dcf24bf..de96cbd16 100644 --- a/src/client/components/google.vue +++ b/src/client/components/google.vue @@ -8,10 +8,8 @@ <script lang="ts"> import Vue from 'vue'; import { faSearch } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, props: ['q'], data() { return { diff --git a/src/client/components/image-viewer.vue b/src/client/components/image-viewer.vue index 3359b600d..c78112b98 100644 --- a/src/client/components/image-viewer.vue +++ b/src/client/components/image-viewer.vue @@ -6,12 +6,9 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import XModal from './modal.vue'; export default Vue.extend({ - i18n, - components: { XModal, }, diff --git a/src/client/components/instance-stats.vue b/src/client/components/instance-stats.vue index 378e9ce39..552e3523f 100644 --- a/src/client/components/instance-stats.vue +++ b/src/client/components/instance-stats.vue @@ -125,7 +125,6 @@ import Vue from 'vue'; import { faChartBar, faUser, faPencilAlt } from '@fortawesome/free-solid-svg-icons'; import Chart from 'chart.js'; -import i18n from '../i18n'; import MkSelect from './ui/select.vue'; const chartLimit = 90; @@ -140,8 +139,6 @@ const alpha = (hex, a) => { }; export default Vue.extend({ - i18n, - components: { MkSelect }, diff --git a/src/client/components/media-banner.vue b/src/client/components/media-banner.vue index 088c11fab..0f746d434 100644 --- a/src/client/components/media-banner.vue +++ b/src/client/components/media-banner.vue @@ -28,10 +28,8 @@ <script lang="ts"> import Vue from 'vue'; import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, props: { media: { type: Object, diff --git a/src/client/components/media-image.vue b/src/client/components/media-image.vue index 6c33b657f..6d1b5345d 100644 --- a/src/client/components/media-image.vue +++ b/src/client/components/media-image.vue @@ -21,12 +21,10 @@ <script lang="ts"> import Vue from 'vue'; import { faExclamationTriangle, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; import { getStaticImageUrl } from '../scripts/get-static-image-url'; import ImageViewer from './image-viewer.vue'; export default Vue.extend({ - i18n, props: { image: { type: Object, diff --git a/src/client/components/media-video.vue b/src/client/components/media-video.vue index d9b4415cb..a5e06bfaa 100644 --- a/src/client/components/media-video.vue +++ b/src/client/components/media-video.vue @@ -23,10 +23,8 @@ import Vue from 'vue'; import { faPlayCircle } from '@fortawesome/free-regular-svg-icons'; import { faExclamationTriangle, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, props: { video: { type: Object, diff --git a/src/client/components/mention.vue b/src/client/components/mention.vue index 06dcf1288..8c939f839 100644 --- a/src/client/components/mention.vue +++ b/src/client/components/mention.vue @@ -16,12 +16,10 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import { toUnicode } from 'punycode'; import { host as localHost } from '../config'; export default Vue.extend({ - i18n, props: { username: { type: String, diff --git a/src/client/components/note.vue b/src/client/components/note.vue index fd895ad5a..6e513a4b2 100644 --- a/src/client/components/note.vue +++ b/src/client/components/note.vue @@ -93,7 +93,6 @@ import { faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, f import { faCopy, faTrashAlt, faEdit, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; import { parse } from '../../mfm/parse'; import { sum, unique } from '../../prelude/array'; -import i18n from '../i18n'; import XSub from './note.sub.vue'; import XNoteHeader from './note-header.vue'; import XNotePreview from './note-preview.vue'; @@ -109,7 +108,6 @@ import { url } from '../config'; import copyToClipboard from '../scripts/copy-to-clipboard'; export default Vue.extend({ - i18n, components: { XSub, diff --git a/src/client/components/notes.vue b/src/client/components/notes.vue index 0cf4dee2d..515bc58e2 100644 --- a/src/client/components/notes.vue +++ b/src/client/components/notes.vue @@ -29,15 +29,12 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import paging from '../scripts/paging'; import XNote from './note.vue'; import XList from './date-separated-list.vue'; import MkButton from './ui/button.vue'; export default Vue.extend({ - i18n, - components: { XNote, XList, MkButton }, diff --git a/src/client/components/notification.vue b/src/client/components/notification.vue index d3ebc8f17..de233d14a 100644 --- a/src/client/components/notification.vue +++ b/src/client/components/notification.vue @@ -61,13 +61,11 @@ import Vue from 'vue'; import { faIdCardAlt, faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faCheck, faPollH } from '@fortawesome/free-solid-svg-icons'; import { faClock } from '@fortawesome/free-regular-svg-icons'; -import getNoteSummary from '../../misc/get-note-summary'; +import noteSummary from '../../misc/get-note-summary'; import XReactionIcon from './reaction-icon.vue'; import MkFollowButton from './follow-button.vue'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, components: { XReactionIcon, MkFollowButton }, @@ -89,7 +87,7 @@ export default Vue.extend({ }, data() { return { - getNoteSummary, + getNoteSummary: (text: string) => noteSummary(text, this.$root.i18n.messages[this.$root.i18n.locale]), followRequestDone: false, groupInviteDone: false, faIdCardAlt, faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faClock, faCheck, faPollH diff --git a/src/client/components/notifications.vue b/src/client/components/notifications.vue index ecf526898..3ed198a04 100644 --- a/src/client/components/notifications.vue +++ b/src/client/components/notifications.vue @@ -18,15 +18,12 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import paging from '../scripts/paging'; import XNotification from './notification.vue'; import XList from './date-separated-list.vue'; import XNote from './note.vue'; export default Vue.extend({ - i18n, - components: { XNotification, XList, diff --git a/src/client/components/page/page.post.vue b/src/client/components/page/page.post.vue index 6f79374f3..da5bc8bfa 100644 --- a/src/client/components/page/page.post.vue +++ b/src/client/components/page/page.post.vue @@ -8,13 +8,11 @@ <script lang="ts"> import Vue from 'vue'; import { faCheck, faPaperPlane } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../i18n'; import MkTextarea from '../ui/textarea.vue'; import MkButton from '../ui/button.vue'; import { apiUrl } from '../../config'; export default Vue.extend({ - i18n, components: { MkTextarea, MkButton, diff --git a/src/client/components/page/page.vue b/src/client/components/page/page.vue index e3b04d7fd..b3cc01ec2 100644 --- a/src/client/components/page/page.vue +++ b/src/client/components/page/page.vue @@ -9,14 +9,11 @@ import Vue from 'vue'; import { parse } from '@syuilo/aiscript'; import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons'; import { faHeart } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../../i18n'; import XBlock from './page.block.vue'; import { Hpml } from '../../scripts/hpml/evaluator'; import { url } from '../../config'; export default Vue.extend({ - i18n, - components: { XBlock }, diff --git a/src/client/components/poll-editor.vue b/src/client/components/poll-editor.vue index 91c7dab59..0687e999b 100644 --- a/src/client/components/poll-editor.vue +++ b/src/client/components/poll-editor.vue @@ -51,7 +51,6 @@ <script lang="ts"> import Vue from 'vue'; import { faExclamationTriangle, faTimes } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; import { erase } from '../../prelude/array'; import { addTime } from '../../prelude/time'; import { formatDateTimeString } from '../../misc/format-time-string'; @@ -61,7 +60,6 @@ import MkSwitch from './ui/switch.vue'; import MkButton from './ui/button.vue'; export default Vue.extend({ - i18n, components: { MkInput, MkSelect, diff --git a/src/client/components/poll.vue b/src/client/components/poll.vue index c748b6b09..e0c42cd00 100644 --- a/src/client/components/poll.vue +++ b/src/client/components/poll.vue @@ -24,11 +24,9 @@ <script lang="ts"> import Vue from 'vue'; import { faCheck } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; import { sum } from '../../prelude/array'; export default Vue.extend({ - i18n, props: { note: { type: Object, diff --git a/src/client/components/post-form-attaches.vue b/src/client/components/post-form-attaches.vue index d9c065361..2415bf28e 100644 --- a/src/client/components/post-form-attaches.vue +++ b/src/client/components/post-form-attaches.vue @@ -14,15 +14,12 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import * as XDraggable from 'vuedraggable'; import { faTimesCircle, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; import { faExclamationTriangle, faICursor } from '@fortawesome/free-solid-svg-icons'; import XFileThumbnail from './drive-file-thumbnail.vue' export default Vue.extend({ - i18n, - components: { XDraggable, XFileThumbnail diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue index 05faea514..cdb61f51d 100644 --- a/src/client/components/post-form.vue +++ b/src/client/components/post-form.vue @@ -57,7 +57,6 @@ import { faEyeSlash, faLaughSquint } from '@fortawesome/free-regular-svg-icons'; import insertTextAtCursor from 'insert-text-at-cursor'; import { length } from 'stringz'; import { toASCII } from 'punycode'; -import i18n from '../i18n'; import MkVisibilityChooser from './visibility-chooser.vue'; import MkUserSelect from './user-select.vue'; import XNotePreview from './note-preview.vue'; @@ -70,8 +69,6 @@ import { formatTimeString } from '../../misc/format-time-string'; import { selectDriveFile } from '../scripts/select-drive-file'; export default Vue.extend({ - i18n, - components: { XNotePreview, XUploader: () => import('./uploader.vue').then(m => m.default), diff --git a/src/client/components/reaction-icon.vue b/src/client/components/reaction-icon.vue index 3c6d56b80..fe2b52836 100644 --- a/src/client/components/reaction-icon.vue +++ b/src/client/components/reaction-icon.vue @@ -4,9 +4,7 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, props: { reaction: { type: String, diff --git a/src/client/components/reaction-picker.vue b/src/client/components/reaction-picker.vue index 99b27ad9c..e331410c3 100644 --- a/src/client/components/reaction-picker.vue +++ b/src/client/components/reaction-picker.vue @@ -11,14 +11,11 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import { emojiRegex } from '../../misc/emoji-regex'; import XReactionIcon from './reaction-icon.vue'; import XPopup from './popup.vue'; export default Vue.extend({ - i18n, - components: { XPopup, XReactionIcon, diff --git a/src/client/components/reactions-viewer.details.vue b/src/client/components/reactions-viewer.details.vue index ea2523a11..67c8b261b 100644 --- a/src/client/components/reactions-viewer.details.vue +++ b/src/client/components/reactions-viewer.details.vue @@ -20,10 +20,8 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, props: { reaction: { type: String, diff --git a/src/client/components/remote-caution.vue b/src/client/components/remote-caution.vue index 95b37d305..21af9f766 100644 --- a/src/client/components/remote-caution.vue +++ b/src/client/components/remote-caution.vue @@ -5,10 +5,8 @@ <script lang="ts"> import Vue from 'vue'; import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, props: { href: { type: String, diff --git a/src/client/components/signin-dialog.vue b/src/client/components/signin-dialog.vue index a356c3ccd..98b75e627 100644 --- a/src/client/components/signin-dialog.vue +++ b/src/client/components/signin-dialog.vue @@ -7,13 +7,10 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import XWindow from './window.vue'; import MkSignin from './signin.vue'; export default Vue.extend({ - i18n, - components: { MkSignin, XWindow, diff --git a/src/client/components/signin.vue b/src/client/components/signin.vue index dc73ad8a0..a7653b17b 100755 --- a/src/client/components/signin.vue +++ b/src/client/components/signin.vue @@ -49,13 +49,10 @@ import { faLock, faGavel } from '@fortawesome/free-solid-svg-icons'; import { faTwitter, faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons'; import MkButton from './ui/button.vue'; import MkInput from './ui/input.vue'; -import i18n from '../i18n'; import { apiUrl, host } from '../config'; import { byteify, hexify } from '../scripts/2fa'; export default Vue.extend({ - i18n, - components: { MkButton, MkInput, diff --git a/src/client/components/signup-dialog.vue b/src/client/components/signup-dialog.vue index 4db79af51..eff1f79c4 100644 --- a/src/client/components/signup-dialog.vue +++ b/src/client/components/signup-dialog.vue @@ -7,13 +7,10 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import XWindow from './window.vue'; import XSignup from './signup.vue'; export default Vue.extend({ - i18n, - components: { XSignup, XWindow, diff --git a/src/client/components/signup.vue b/src/client/components/signup.vue index acb6a745a..ff1932b42 100644 --- a/src/client/components/signup.vue +++ b/src/client/components/signup.vue @@ -53,15 +53,12 @@ import Vue from 'vue'; import { faLock, faExclamationTriangle, faSpinner, faCheck, faKey } from '@fortawesome/free-solid-svg-icons'; const getPasswordStrength = require('syuilo-password-strength'); import { toUnicode } from 'punycode'; -import i18n from '../i18n'; import { host, url } from '../config'; import MkButton from './ui/button.vue'; import MkInput from './ui/input.vue'; import MkSwitch from './ui/switch.vue'; export default Vue.extend({ - i18n, - components: { MkButton, MkInput, diff --git a/src/client/components/stream-indicator.vue b/src/client/components/stream-indicator.vue index dd7a5d07c..ec00f4cbf 100644 --- a/src/client/components/stream-indicator.vue +++ b/src/client/components/stream-indicator.vue @@ -10,10 +10,8 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, data() { return { hasDisconnected: false, diff --git a/src/client/components/sub-note-content.vue b/src/client/components/sub-note-content.vue index e60c19744..a14c832ea 100644 --- a/src/client/components/sub-note-content.vue +++ b/src/client/components/sub-note-content.vue @@ -21,12 +21,10 @@ <script lang="ts"> import Vue from 'vue'; import { faReply } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; import XPoll from './poll.vue'; import XMediaList from './media-list.vue'; export default Vue.extend({ - i18n, components: { XPoll, XMediaList, diff --git a/src/client/components/time.vue b/src/client/components/time.vue index 6d092cf4f..2a871d6d8 100644 --- a/src/client/components/time.vue +++ b/src/client/components/time.vue @@ -8,10 +8,8 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, props: { time: { type: [Date, String], diff --git a/src/client/components/uploader.vue b/src/client/components/uploader.vue index 4ceb5e287..6ebdf123b 100644 --- a/src/client/components/uploader.vue +++ b/src/client/components/uploader.vue @@ -21,13 +21,11 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import { apiUrl } from '../config'; //import getMD5 from '../../scripts/get-md5'; import { faSpinner } from '@fortawesome/free-solid-svg-icons'; export default Vue.extend({ - i18n, data() { return { uploads: [], diff --git a/src/client/components/url-preview-popup.vue b/src/client/components/url-preview-popup.vue index acd9b1aa9..52731296c 100644 --- a/src/client/components/url-preview-popup.vue +++ b/src/client/components/url-preview-popup.vue @@ -6,12 +6,9 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import MkUrlPreview from './url-preview.vue'; export default Vue.extend({ - i18n, - components: { MkUrlPreview }, diff --git a/src/client/components/url-preview.vue b/src/client/components/url-preview.vue index c2dd0038b..d77cfafd1 100644 --- a/src/client/components/url-preview.vue +++ b/src/client/components/url-preview.vue @@ -32,12 +32,9 @@ <script lang="ts"> import Vue from 'vue'; import { faPlayCircle } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../i18n'; import { url as local, lang } from '../config'; export default Vue.extend({ - i18n, - props: { url: { type: String, diff --git a/src/client/components/user-list.vue b/src/client/components/user-list.vue index bde3af690..7a9cd58a4 100644 --- a/src/client/components/user-list.vue +++ b/src/client/components/user-list.vue @@ -31,14 +31,11 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import paging from '../scripts/paging'; import MkContainer from './ui/container.vue'; import MkFollowButton from './follow-button.vue'; export default Vue.extend({ - i18n, - components: { MkContainer, MkFollowButton, diff --git a/src/client/components/user-menu.vue b/src/client/components/user-menu.vue index a2275197d..25937fb3c 100644 --- a/src/client/components/user-menu.vue +++ b/src/client/components/user-menu.vue @@ -6,15 +6,12 @@ import Vue from 'vue'; import { faAt, faListUl, faEye, faEyeSlash, faBan, faPencilAlt, faComments, faUsers, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons'; import { faSnowflake, faEnvelope } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../i18n'; import XMenu from './menu.vue'; import copyToClipboard from '../scripts/copy-to-clipboard'; import { host } from '../config'; import getAcct from '../../misc/acct/render'; export default Vue.extend({ - i18n, - components: { XMenu }, diff --git a/src/client/components/user-preview.vue b/src/client/components/user-preview.vue index 89150eaac..8c8eee2a3 100644 --- a/src/client/components/user-preview.vue +++ b/src/client/components/user-preview.vue @@ -28,13 +28,10 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import parseAcct from '../../misc/acct/parse'; import MkFollowButton from './follow-button.vue'; export default Vue.extend({ - i18n, - components: { MkFollowButton }, diff --git a/src/client/components/user-select.vue b/src/client/components/user-select.vue index a82626652..9b4a68ddb 100644 --- a/src/client/components/user-select.vue +++ b/src/client/components/user-select.vue @@ -21,14 +21,11 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import { faTimes, faCheck } from '@fortawesome/free-solid-svg-icons'; import MkInput from './ui/input.vue'; import XWindow from './window.vue'; export default Vue.extend({ - i18n, - components: { MkInput, XWindow, diff --git a/src/client/components/users-dialog.vue b/src/client/components/users-dialog.vue index 9d0c5e425..0e0cc36c2 100644 --- a/src/client/components/users-dialog.vue +++ b/src/client/components/users-dialog.vue @@ -31,13 +31,10 @@ <script lang="ts"> import Vue from 'vue'; import { faTimes } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; import paging from '../scripts/paging'; import XModal from './modal.vue'; export default Vue.extend({ - i18n, - components: { XModal, }, diff --git a/src/client/components/visibility-chooser.vue b/src/client/components/visibility-chooser.vue index dc7b41e28..0f7e37a08 100644 --- a/src/client/components/visibility-chooser.vue +++ b/src/client/components/visibility-chooser.vue @@ -37,11 +37,9 @@ import Vue from 'vue'; import { faGlobe, faUnlock, faHome } from '@fortawesome/free-solid-svg-icons'; import { faEnvelope } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../i18n'; import XPopup from './popup.vue'; export default Vue.extend({ - i18n, components: { XPopup }, diff --git a/src/client/components/window.vue b/src/client/components/window.vue index 0b2ba248b..db1398518 100644 --- a/src/client/components/window.vue +++ b/src/client/components/window.vue @@ -20,12 +20,9 @@ <script lang="ts"> import Vue from 'vue'; import { faTimes, faCheck } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; import XModal from './modal.vue'; export default Vue.extend({ - i18n, - components: { XModal, }, diff --git a/src/client/config.ts b/src/client/config.ts index 0d4a96964..f71647a05 100644 --- a/src/client/config.ts +++ b/src/client/config.ts @@ -1,3 +1,6 @@ +import { clientDb, entries } from './db'; +import { fromEntries } from '../prelude/array'; + declare const _LANGS_: string[]; declare const _VERSION_: string; declare const _ENV_: string; @@ -12,7 +15,7 @@ export const apiUrl = url + '/api'; export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://') + '/streaming'; export const lang = localStorage.getItem('lang'); export const langs = _LANGS_; -export const locale = JSON.parse(localStorage.getItem('locale')); +export const getLocale = async () => fromEntries((await entries(clientDb.i18n)) as [string, string][]); export const version = _VERSION_; export const env = _ENV_; export const instanceName = siteName === 'Misskey' ? null : siteName; diff --git a/src/client/db.ts b/src/client/db.ts new file mode 100644 index 000000000..3000a0c96 --- /dev/null +++ b/src/client/db.ts @@ -0,0 +1,68 @@ +import { Store } from 'idb-keyval'; +// Provide functions from idb-keyval +export { get, set, del, clear, keys } from 'idb-keyval'; + +//#region Construct DB +export const clientDb = { + i18n: new Store('MisskeyClient', 'i18n') +}; +//#endregion + +//#region Provide some tool functions +function openTransaction(store: Store, mode: IDBTransactionMode): Promise<IDBTransaction>{ + return store._dbp.then(db => db.transaction(store.storeName, mode)); +} + +export function entries(store: Store): Promise<[IDBValidKey, unknown][]> { + const entries: [IDBValidKey, unknown][] = []; + + return store._withIDBStore('readonly', store => { + store.openCursor().onsuccess = function () { + if (!this.result) return; + entries.push([this.result.key, this.result.value]); + this.result.continue(); + }; + }).then(() => entries); +} + +export async function bulkGet(keys: IDBValidKey[], store: Store): Promise<[IDBValidKey, unknown][]> { + const valPromises: Promise<[IDBValidKey, unknown]>[] = []; + + const tx = await openTransaction(store, 'readwrite'); + const st = tx.objectStore(store.storeName); + for (const key of keys) { + valPromises.push(new Promise((resolve, reject) => { + const getting = st.get(key); + getting.onsuccess = function (e) { + return resolve([key, this.result]); + }; + getting.onerror = function (e) { + return reject(this.error); + }; + })); + } + return new Promise((resolve, reject) => { + tx.oncomplete = () => resolve(Promise.all(valPromises)); + tx.abort = tx.onerror = () => reject(tx.error); + }); +} + +export async function bulkSet(map: [IDBValidKey, any][], store: Store): Promise<void> { + const tx = await openTransaction(store, 'readwrite'); + const st = tx.objectStore(store.storeName); + for (const [key, value] of map) { + st.put(value, key); + } + return new Promise((resolve, reject) => { + tx.oncomplete = () => resolve(); + tx.abort = tx.onerror = () => reject(tx.error); + }); +} + +export function count(store: Store): Promise<number> { + let req: IDBRequest<number>; + return store._withIDBStore('readonly', store => { + req = store.count(); + }).then(() => req.result); +} +//#endregion diff --git a/src/client/i18n.ts b/src/client/i18n.ts deleted file mode 100644 index 05d319fba..000000000 --- a/src/client/i18n.ts +++ /dev/null @@ -1,12 +0,0 @@ -import Vue from 'vue'; -import VueI18n from 'vue-i18n'; -import { lang, locale } from './config'; - -Vue.use(VueI18n); - -export default new VueI18n({ - locale: lang, - messages: { - [lang]: locale - } -}); diff --git a/src/client/init.ts b/src/client/init.ts index 500092061..e2772502f 100644 --- a/src/client/init.ts +++ b/src/client/init.ts @@ -7,13 +7,13 @@ import Vuex from 'vuex'; import VueMeta from 'vue-meta'; import PortalVue from 'portal-vue'; import VAnimateCss from 'v-animate-css'; +import VueI18n from 'vue-i18n'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; -import i18n from './i18n'; import VueHotkey from './scripts/hotkey'; import App from './app.vue'; import MiOS from './mios'; -import { version, langs, instanceName } from './config'; +import { version, langs, instanceName, getLocale } from './config'; import PostFormDialog from './components/post-form-dialog.vue'; import Dialog from './components/dialog.vue'; import Menu from './components/menu.vue'; @@ -21,12 +21,15 @@ import { router } from './router'; import { applyTheme, lightTheme } from './theme'; import { isDeviceDarkmode } from './scripts/is-device-darkmode'; import createStore from './store'; +import { clientDb, get, count } from './db'; +import { setI18nContexts } from './scripts/set-i18n-contexts'; Vue.use(Vuex); Vue.use(VueHotkey); Vue.use(VueMeta); Vue.use(PortalVue); Vue.use(VAnimateCss); +Vue.use(VueI18n); Vue.component('fa', FontAwesomeIcon); require('./directives'); @@ -96,27 +99,6 @@ if (isMobile || window.innerWidth <= 1024) { head.appendChild(viewport); } -//#region Fetch locale data -const cachedLocale = localStorage.getItem('locale'); - -if (cachedLocale == null) { - fetch(`/assets/locales/${lang}.${version}.json`) - .then(response => response.json()).then(locale => { - localStorage.setItem('locale', JSON.stringify(locale)); - i18n.locale = lang; - i18n.setLocaleMessage(lang, locale); - }); -} else { - // TODO: 古い時だけ更新 - setTimeout(() => { - fetch(`/assets/locales/${lang}.${version}.json`) - .then(response => response.json()).then(locale => { - localStorage.setItem('locale', JSON.stringify(locale)); - }); - }, 1000 * 5); -} -//#endregion - //#region Set lang attr const html = document.documentElement; html.setAttribute('lang', lang); @@ -167,6 +149,18 @@ os.init(async () => { }); //#endregion + //#region Fetch locale data + const i18n = new VueI18n(); + + await count(clientDb.i18n).then(async n => { + if (n === 0) return setI18nContexts(lang, version, i18n); + if ((await get('_version_', clientDb.i18n) !== version)) return setI18nContexts(lang, version, i18n, true); + + i18n.locale = lang; + i18n.setLocaleMessage(lang, await getLocale()); + }); + //#endregion + if ('Notification' in window && store.getters.isSignedIn) { // 許可を得ていなかったらリクエスト if (Notification.permission === 'default') { @@ -176,6 +170,7 @@ os.init(async () => { const app = new Vue({ store: store, + i18n, metaInfo: { title: null, titleTemplate: title => title ? `${title} | ${(instanceName || 'Misskey')}` : (instanceName || 'Misskey') @@ -183,7 +178,8 @@ os.init(async () => { data() { return { stream: os.stream, - isMobile: isMobile + isMobile: isMobile, + i18n // TODO: 消せないか考える SEE: https://github.com/syuilo/misskey/pull/6396#discussion_r429511030 }; }, methods: { diff --git a/src/client/pages/about-misskey.vue b/src/client/pages/about-misskey.vue index 84cd5d5e9..2c4a257b1 100644 --- a/src/client/pages/about-misskey.vue +++ b/src/client/pages/about-misskey.vue @@ -63,12 +63,9 @@ import Vue from 'vue'; import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; import { version } from '../config'; -import i18n from '../i18n'; import MkLink from '../components/link.vue'; export default Vue.extend({ - i18n, - components: { MkLink }, diff --git a/src/client/pages/about.vue b/src/client/pages/about.vue index a3a4b6ac7..25fb0ca13 100644 --- a/src/client/pages/about.vue +++ b/src/client/pages/about.vue @@ -25,12 +25,9 @@ import Vue from 'vue'; import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; import { version } from '../config'; -import i18n from '../i18n'; import MkInstanceStats from '../components/instance-stats.vue'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('instance') as string diff --git a/src/client/pages/announcements.vue b/src/client/pages/announcements.vue index 5c6d4f58a..089475ed6 100644 --- a/src/client/pages/announcements.vue +++ b/src/client/pages/announcements.vue @@ -21,13 +21,10 @@ <script lang="ts"> import Vue from 'vue'; import { faCheck, faBroadcastTower } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; import MkPagination from '../components/ui/pagination.vue'; import MkButton from '../components/ui/button.vue'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('announcements') as string diff --git a/src/client/pages/auth.form.vue b/src/client/pages/auth.form.vue index e6f61f52f..c5a9b769a 100644 --- a/src/client/pages/auth.form.vue +++ b/src/client/pages/auth.form.vue @@ -23,11 +23,9 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import MkButton from '../components/ui/button.vue'; export default Vue.extend({ - i18n, components: { MkButton }, diff --git a/src/client/pages/auth.vue b/src/client/pages/auth.vue index e025924fe..5c40842da 100755 --- a/src/client/pages/auth.vue +++ b/src/client/pages/auth.vue @@ -30,12 +30,10 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import XForm from './auth.form.vue'; import MkSignin from '../components/signin.vue'; export default Vue.extend({ - i18n, components: { XForm, MkSignin, diff --git a/src/client/pages/doc.vue b/src/client/pages/doc.vue index 7c4f7ebcc..e4c4ef5c6 100644 --- a/src/client/pages/doc.vue +++ b/src/client/pages/doc.vue @@ -19,7 +19,6 @@ import Vue from 'vue'; import { faFileAlt } from '@fortawesome/free-solid-svg-icons' import MarkdownIt from 'markdown-it'; import MarkdownItAnchor from 'markdown-it-anchor'; -import i18n from '../i18n'; import { url, lang } from '../config'; import MkLink from '../components/link.vue'; @@ -32,8 +31,6 @@ markdown.use(MarkdownItAnchor, { }); export default Vue.extend({ - i18n, - metaInfo() { return { title: this.title, diff --git a/src/client/pages/explore.vue b/src/client/pages/explore.vue index 7ff4b5ed6..39904846c 100644 --- a/src/client/pages/explore.vue +++ b/src/client/pages/explore.vue @@ -57,13 +57,10 @@ import Vue from 'vue'; import { faChartLine, faPlus, faHashtag, faRocket } from '@fortawesome/free-solid-svg-icons'; import { faBookmark, faCommentAlt } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../i18n'; import XUserList from '../components/user-list.vue'; import MkContainer from '../components/ui/container.vue'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('explore') as string diff --git a/src/client/pages/follow.vue b/src/client/pages/follow.vue index d76525973..8659763bb 100644 --- a/src/client/pages/follow.vue +++ b/src/client/pages/follow.vue @@ -5,11 +5,8 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, - created() { const acct = new URL(location.href).searchParams.get('acct'); if (acct == null) return; diff --git a/src/client/pages/index.welcome.entrance.vue b/src/client/pages/index.welcome.entrance.vue index a9343e87c..9bb2e85fc 100644 --- a/src/client/pages/index.welcome.entrance.vue +++ b/src/client/pages/index.welcome.entrance.vue @@ -20,12 +20,9 @@ import XSigninDialog from '../components/signin-dialog.vue'; import XSignupDialog from '../components/signup-dialog.vue'; import MkButton from '../components/ui/button.vue'; import XNotes from '../components/notes.vue'; -import i18n from '../i18n'; import { host } from '../config'; export default Vue.extend({ - i18n, - components: { MkButton, XNotes, diff --git a/src/client/pages/index.welcome.setup.vue b/src/client/pages/index.welcome.setup.vue index 6d08f5b5d..9a66a4dff 100644 --- a/src/client/pages/index.welcome.setup.vue +++ b/src/client/pages/index.welcome.setup.vue @@ -25,10 +25,8 @@ import { faLock } from '@fortawesome/free-solid-svg-icons'; import MkButton from '../components/ui/button.vue'; import MkInput from '../components/ui/input.vue'; import { host } from '../config'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, components: { MkButton, diff --git a/src/client/pages/instance/announcements.vue b/src/client/pages/instance/announcements.vue index 2889cf8cc..0e11e2932 100644 --- a/src/client/pages/instance/announcements.vue +++ b/src/client/pages/instance/announcements.vue @@ -28,14 +28,11 @@ import Vue from 'vue'; import { faBroadcastTower, faPlus } from '@fortawesome/free-solid-svg-icons'; import { faSave, faTrashAlt } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../../i18n'; import MkButton from '../../components/ui/button.vue'; import MkInput from '../../components/ui/input.vue'; import MkTextarea from '../../components/ui/textarea.vue'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('announcements') as string diff --git a/src/client/pages/instance/federation.instance.vue b/src/client/pages/instance/federation.instance.vue index 08f4d1b4f..6b6352a15 100644 --- a/src/client/pages/instance/federation.instance.vue +++ b/src/client/pages/instance/federation.instance.vue @@ -120,7 +120,6 @@ <script lang="ts"> import Vue from 'vue'; import Chart from 'chart.js'; -import i18n from '../../i18n'; import { faTimes, faCrosshairs, faCloudDownloadAlt, faCloudUploadAlt, faUsers, faPencilAlt, faFileImage, faDatabase, faTrafficLight, faLongArrowAltUp, faLongArrowAltDown, faMinusCircle, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import XWindow from '../../components/window.vue'; import MkUsersDialog from '../../components/users-dialog.vue'; @@ -141,8 +140,6 @@ const alpha = hex => { }; export default Vue.extend({ - i18n, - components: { XWindow, MkSelect, diff --git a/src/client/pages/instance/federation.vue b/src/client/pages/instance/federation.vue index 5babc6045..77819235d 100644 --- a/src/client/pages/instance/federation.vue +++ b/src/client/pages/instance/federation.vue @@ -62,7 +62,6 @@ <script lang="ts"> import Vue from 'vue'; import { faGlobe, faCircle, faExchangeAlt, faCaretDown, faCaretUp, faTrafficLight } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../i18n'; import MkButton from '../../components/ui/button.vue'; import MkInput from '../../components/ui/input.vue'; import MkSelect from '../../components/ui/select.vue'; @@ -70,8 +69,6 @@ import MkPagination from '../../components/ui/pagination.vue'; import MkInstanceInfo from './federation.instance.vue'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('federation') as string diff --git a/src/client/pages/instance/index.vue b/src/client/pages/instance/index.vue index 1d90aa553..d21f8d455 100644 --- a/src/client/pages/instance/index.vue +++ b/src/client/pages/instance/index.vue @@ -107,7 +107,6 @@ import MkButton from '../../components/ui/button.vue'; import MkSelect from '../../components/ui/select.vue'; import MkInput from '../../components/ui/input.vue'; import { version, url } from '../../config'; -import i18n from '../../i18n'; const alpha = (hex, a) => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!; @@ -118,8 +117,6 @@ const alpha = (hex, a) => { }; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('instance') as string diff --git a/src/client/pages/instance/queue.queue.vue b/src/client/pages/instance/queue.queue.vue index 7f0fc7d2b..1649d1e17 100644 --- a/src/client/pages/instance/queue.queue.vue +++ b/src/client/pages/instance/queue.queue.vue @@ -25,7 +25,6 @@ <script lang="ts"> import Vue from 'vue'; import Chart from 'chart.js'; -import i18n from '../../i18n'; const alpha = (hex, a) => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!; @@ -36,8 +35,6 @@ const alpha = (hex, a) => { }; export default Vue.extend({ - i18n, - props: { domain: { required: true diff --git a/src/client/pages/instance/queue.vue b/src/client/pages/instance/queue.vue index c4892e88d..7a2204e51 100644 --- a/src/client/pages/instance/queue.vue +++ b/src/client/pages/instance/queue.vue @@ -21,13 +21,10 @@ import Vue from 'vue'; import { faExchangeAlt } from '@fortawesome/free-solid-svg-icons'; import { faTrashAlt } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../../i18n'; import MkButton from '../../components/ui/button.vue'; import XQueue from './queue.queue.vue'; export default Vue.extend({ - i18n, - metaInfo() { return { title: `${this.$t('jobQueue')} | ${this.$t('instance')}` diff --git a/src/client/pages/instance/relays.vue b/src/client/pages/instance/relays.vue index 9b523bd0e..dd18867b6 100644 --- a/src/client/pages/instance/relays.vue +++ b/src/client/pages/instance/relays.vue @@ -28,13 +28,10 @@ import Vue from 'vue'; import { faPlus, faProjectDiagram } from '@fortawesome/free-solid-svg-icons'; import { faSave, faTrashAlt } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../../i18n'; import MkButton from '../../components/ui/button.vue'; import MkInput from '../../components/ui/input.vue'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('relays') as string diff --git a/src/client/pages/instance/settings.vue b/src/client/pages/instance/settings.vue index afd6d4cc6..0436e8780 100644 --- a/src/client/pages/instance/settings.vue +++ b/src/client/pages/instance/settings.vue @@ -210,12 +210,9 @@ import MkSwitch from '../../components/ui/switch.vue'; import MkInfo from '../../components/ui/info.vue'; import MkUserSelect from '../../components/user-select.vue'; import { url } from '../../config'; -import i18n from '../../i18n'; import getAcct from '../../../misc/acct/render'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('instance') as string diff --git a/src/client/pages/instance/users.user.vue b/src/client/pages/instance/users.user.vue index 1fb064f7f..25f026063 100644 --- a/src/client/pages/instance/users.user.vue +++ b/src/client/pages/instance/users.user.vue @@ -39,12 +39,9 @@ import { faTimes, faBookmark, faKey, faSync, faMicrophoneSlash, faExternalLinkSq import { faSnowflake, faTrashAlt, faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons'; import MkButton from '../../components/ui/button.vue'; import MkSwitch from '../../components/ui/switch.vue'; -import i18n from '../../i18n'; import Progress from '../../scripts/loading'; export default Vue.extend({ - i18n, - components: { MkButton, MkSwitch, diff --git a/src/client/pages/messaging/index.vue b/src/client/pages/messaging/index.vue index 7a55004cb..7ee782c4a 100644 --- a/src/client/pages/messaging/index.vue +++ b/src/client/pages/messaging/index.vue @@ -42,14 +42,11 @@ <script lang="ts"> import Vue from 'vue'; import { faUser, faUsers, faComments, faPlus } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../i18n'; import getAcct from '../../../misc/acct/render'; import MkButton from '../../components/ui/button.vue'; import MkUserSelect from '../../components/user-select.vue'; export default Vue.extend({ - i18n, - components: { MkButton }, diff --git a/src/client/pages/messaging/messaging-room.form.vue b/src/client/pages/messaging/messaging-room.form.vue index 0cd3dfcc8..be47efd2c 100644 --- a/src/client/pages/messaging/messaging-room.form.vue +++ b/src/client/pages/messaging/messaging-room.form.vue @@ -27,12 +27,10 @@ import Vue from 'vue'; import { faPaperPlane, faPhotoVideo, faLaughSquint } from '@fortawesome/free-solid-svg-icons'; import insertTextAtCursor from 'insert-text-at-cursor'; import * as autosize from 'autosize'; -import i18n from '../../i18n'; import { formatTimeString } from '../../../misc/format-time-string'; import { selectFile } from '../../scripts/select-file'; export default Vue.extend({ - i18n, components: { XUploader: () => import('../../components/uploader.vue').then(m => m.default), }, diff --git a/src/client/pages/messaging/messaging-room.message.vue b/src/client/pages/messaging/messaging-room.message.vue index 67756572f..58e1e54ad 100644 --- a/src/client/pages/messaging/messaging-room.message.vue +++ b/src/client/pages/messaging/messaging-room.message.vue @@ -38,13 +38,11 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../../i18n'; import { parse } from '../../../mfm/parse'; import { unique } from '../../../prelude/array'; import MkUrlPreview from '../../components/url-preview.vue'; export default Vue.extend({ - i18n, components: { MkUrlPreview }, diff --git a/src/client/pages/messaging/messaging-room.vue b/src/client/pages/messaging/messaging-room.vue index 317ad087f..e97d5532a 100644 --- a/src/client/pages/messaging/messaging-room.vue +++ b/src/client/pages/messaging/messaging-room.vue @@ -37,7 +37,6 @@ <script lang="ts"> import Vue from 'vue'; import { faArrowCircleDown, faFlag, faUsers, faInfoCircle } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../i18n'; import XList from '../../components/date-separated-list.vue'; import XMessage from './messaging-room.message.vue'; import XForm from './messaging-room.form.vue'; @@ -45,8 +44,6 @@ import { url } from '../../config'; import parseAcct from '../../../misc/acct/parse'; export default Vue.extend({ - i18n, - components: { XMessage, XForm, diff --git a/src/client/pages/miauth.vue b/src/client/pages/miauth.vue index 0e170af11..15cde8bc2 100644 --- a/src/client/pages/miauth.vue +++ b/src/client/pages/miauth.vue @@ -40,12 +40,10 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; import MkSignin from '../components/signin.vue'; import MkButton from '../components/ui/button.vue'; export default Vue.extend({ - i18n, components: { MkSignin, MkButton, diff --git a/src/client/pages/my-antennas/index.antenna.vue b/src/client/pages/my-antennas/index.antenna.vue index 2a9aebbcb..6435e4fc9 100644 --- a/src/client/pages/my-antennas/index.antenna.vue +++ b/src/client/pages/my-antennas/index.antenna.vue @@ -48,7 +48,6 @@ <script lang="ts"> import Vue from 'vue'; import { faSave, faTrash } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../i18n'; import MkButton from '../../components/ui/button.vue'; import MkInput from '../../components/ui/input.vue'; import MkTextarea from '../../components/ui/textarea.vue'; @@ -58,8 +57,6 @@ import MkUserSelect from '../../components/user-select.vue'; import getAcct from '../../../misc/acct/render'; export default Vue.extend({ - i18n, - components: { MkButton, MkInput, MkTextarea, MkSelect, MkSwitch }, diff --git a/src/client/pages/my-groups/group.vue b/src/client/pages/my-groups/group.vue index c8170a2a5..0132bc2c3 100644 --- a/src/client/pages/my-groups/group.vue +++ b/src/client/pages/my-groups/group.vue @@ -41,14 +41,11 @@ <script lang="ts"> import Vue from 'vue'; import { faTimes, faUsers } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../i18n'; import Progress from '../../scripts/loading'; import MkButton from '../../components/ui/button.vue'; import MkUserSelect from '../../components/user-select.vue'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.group ? `${this.group.name} | ${this.$t('manageGroups')}` : this.$t('manageGroups') diff --git a/src/client/pages/my-lists/list.vue b/src/client/pages/my-lists/list.vue index cf85a80cc..7052c5516 100644 --- a/src/client/pages/my-lists/list.vue +++ b/src/client/pages/my-lists/list.vue @@ -40,14 +40,11 @@ <script lang="ts"> import Vue from 'vue'; import { faTimes, faListUl } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../i18n'; import Progress from '../../scripts/loading'; import MkButton from '../../components/ui/button.vue'; import MkUserSelect from '../../components/user-select.vue'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.list ? `${this.list.name} | ${this.$t('manageLists')}` : this.$t('manageLists') diff --git a/src/client/pages/my-settings/2fa.vue b/src/client/pages/my-settings/2fa.vue index 6ceca21fe..58ba03c41 100644 --- a/src/client/pages/my-settings/2fa.vue +++ b/src/client/pages/my-settings/2fa.vue @@ -65,7 +65,6 @@ <script lang="ts"> import Vue from 'vue'; import { faLock } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../i18n'; import { hostname } from '../../config'; import { byteify, hexify, stringify } from '../../scripts/2fa'; import MkButton from '../../components/ui/button.vue'; @@ -74,7 +73,6 @@ import MkInput from '../../components/ui/input.vue'; import MkSwitch from '../../components/ui/switch.vue'; export default Vue.extend({ - i18n, components: { MkButton, MkInfo, MkInput, MkSwitch }, diff --git a/src/client/pages/my-settings/api.vue b/src/client/pages/my-settings/api.vue index f394c826d..79b459fb5 100644 --- a/src/client/pages/my-settings/api.vue +++ b/src/client/pages/my-settings/api.vue @@ -13,12 +13,10 @@ <script lang="ts"> import Vue from 'vue'; import { faKey, faSyncAlt } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../i18n'; import MkButton from '../../components/ui/button.vue'; import MkInput from '../../components/ui/input.vue'; export default Vue.extend({ - i18n, components: { MkButton, MkInput }, diff --git a/src/client/pages/my-settings/drive.vue b/src/client/pages/my-settings/drive.vue index c3d2d1dc2..7612c5011 100644 --- a/src/client/pages/my-settings/drive.vue +++ b/src/client/pages/my-settings/drive.vue @@ -13,12 +13,9 @@ import Vue from 'vue'; import { faCloud, faFolderOpen } from '@fortawesome/free-solid-svg-icons'; import { faClock, faEyeSlash, faTrashAlt } from '@fortawesome/free-regular-svg-icons'; import MkButton from '../../components/ui/button.vue'; -import i18n from '../../i18n'; import { selectDriveFolder } from '../../scripts/select-drive-folder'; export default Vue.extend({ - i18n, - components: { MkButton, }, diff --git a/src/client/pages/my-settings/import-export.vue b/src/client/pages/my-settings/import-export.vue index 479574118..cc148d48d 100644 --- a/src/client/pages/my-settings/import-export.vue +++ b/src/client/pages/my-settings/import-export.vue @@ -21,12 +21,9 @@ import Vue from 'vue'; import { faDownload, faUpload, faBoxes } from '@fortawesome/free-solid-svg-icons'; import MkButton from '../../components/ui/button.vue'; import MkSelect from '../../components/ui/select.vue'; -import i18n from '../../i18n'; import { apiUrl } from '../../config'; export default Vue.extend({ - i18n, - components: { MkButton, MkSelect, diff --git a/src/client/pages/my-settings/integration.vue b/src/client/pages/my-settings/integration.vue index 3dd7783f1..2d6e57e22 100644 --- a/src/client/pages/my-settings/integration.vue +++ b/src/client/pages/my-settings/integration.vue @@ -29,13 +29,10 @@ import Vue from 'vue'; import { faShareAlt } from '@fortawesome/free-solid-svg-icons'; import { faTwitter, faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons'; -import i18n from '../../i18n'; import { apiUrl } from '../../config'; import MkButton from '../../components/ui/button.vue'; export default Vue.extend({ - i18n, - components: { MkButton }, diff --git a/src/client/pages/my-settings/mute-block.vue b/src/client/pages/my-settings/mute-block.vue index 03cf4aacc..8eb43a6e2 100644 --- a/src/client/pages/my-settings/mute-block.vue +++ b/src/client/pages/my-settings/mute-block.vue @@ -34,11 +34,8 @@ import Vue from 'vue'; import { faBan } from '@fortawesome/free-solid-svg-icons'; import MkPagination from '../../components/ui/pagination.vue'; -import i18n from '../../i18n'; export default Vue.extend({ - i18n, - components: { MkPagination, }, diff --git a/src/client/pages/my-settings/privacy.vue b/src/client/pages/my-settings/privacy.vue index 7ac9062d8..527ac9ea3 100644 --- a/src/client/pages/my-settings/privacy.vue +++ b/src/client/pages/my-settings/privacy.vue @@ -24,11 +24,8 @@ import Vue from 'vue'; import { faLock } from '@fortawesome/free-solid-svg-icons'; import MkSelect from '../../components/ui/select.vue'; import MkSwitch from '../../components/ui/switch.vue'; -import i18n from '../../i18n'; export default Vue.extend({ - i18n, - components: { MkSelect, MkSwitch, diff --git a/src/client/pages/my-settings/profile.vue b/src/client/pages/my-settings/profile.vue index b168c89ec..16bba7a27 100644 --- a/src/client/pages/my-settings/profile.vue +++ b/src/client/pages/my-settings/profile.vue @@ -62,13 +62,10 @@ import MkButton from '../../components/ui/button.vue'; import MkInput from '../../components/ui/input.vue'; import MkTextarea from '../../components/ui/textarea.vue'; import MkSwitch from '../../components/ui/switch.vue'; -import i18n from '../../i18n'; import { host } from '../../config'; import { selectFile } from '../../scripts/select-file'; export default Vue.extend({ - i18n, - components: { MkButton, MkInput, diff --git a/src/client/pages/my-settings/reaction.vue b/src/client/pages/my-settings/reaction.vue index 68c481707..ef4f6f672 100644 --- a/src/client/pages/my-settings/reaction.vue +++ b/src/client/pages/my-settings/reaction.vue @@ -21,13 +21,10 @@ import { faUndo } from '@fortawesome/free-solid-svg-icons'; import MkInput from '../../components/ui/input.vue'; import MkButton from '../../components/ui/button.vue'; import MkReactionPicker from '../../components/reaction-picker.vue'; -import i18n from '../../i18n'; import { emojiRegexWithCustom } from '../../../misc/emoji-regex'; import { defaultSettings } from '../../store'; export default Vue.extend({ - i18n, - components: { MkInput, MkButton, diff --git a/src/client/pages/my-settings/security.vue b/src/client/pages/my-settings/security.vue index ba670b2f6..dc77ca12c 100644 --- a/src/client/pages/my-settings/security.vue +++ b/src/client/pages/my-settings/security.vue @@ -11,11 +11,8 @@ import Vue from 'vue'; import { faLock } from '@fortawesome/free-solid-svg-icons'; import MkButton from '../../components/ui/button.vue'; -import i18n from '../../i18n'; export default Vue.extend({ - i18n, - components: { MkButton, }, diff --git a/src/client/pages/not-found.vue b/src/client/pages/not-found.vue index 9608e0778..7f4c46c23 100644 --- a/src/client/pages/not-found.vue +++ b/src/client/pages/not-found.vue @@ -15,11 +15,8 @@ <script lang="ts"> import Vue from 'vue'; import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('notFound') as string diff --git a/src/client/pages/note.vue b/src/client/pages/note.vue index 37c66833e..48629a4eb 100644 --- a/src/client/pages/note.vue +++ b/src/client/pages/note.vue @@ -29,14 +29,12 @@ <script lang="ts"> import Vue from 'vue'; import { faChevronUp, faChevronDown } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; import Progress from '../scripts/loading'; import XNote from '../components/note.vue'; import XNotes from '../components/notes.vue'; import MkRemoteCaution from '../components/remote-caution.vue'; export default Vue.extend({ - i18n, metaInfo() { return { title: this.$t('note') as string diff --git a/src/client/pages/page-editor/els/page-editor.el.button.vue b/src/client/pages/page-editor/els/page-editor.el.button.vue index 9ca9fe06f..982120166 100644 --- a/src/client/pages/page-editor/els/page-editor.el.button.vue +++ b/src/client/pages/page-editor/els/page-editor.el.button.vue @@ -40,15 +40,12 @@ <script lang="ts"> import Vue from 'vue'; import { faBolt } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; import MkSelect from '../../../components/ui/select.vue'; import MkInput from '../../../components/ui/input.vue'; import MkSwitch from '../../../components/ui/switch.vue'; export default Vue.extend({ - i18n, - components: { XContainer, MkSelect, MkInput, MkSwitch }, diff --git a/src/client/pages/page-editor/els/page-editor.el.canvas.vue b/src/client/pages/page-editor/els/page-editor.el.canvas.vue index 497731891..a49920780 100644 --- a/src/client/pages/page-editor/els/page-editor.el.canvas.vue +++ b/src/client/pages/page-editor/els/page-editor.el.canvas.vue @@ -13,13 +13,10 @@ <script lang="ts"> import Vue from 'vue'; import { faPaintBrush, faMagic } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; import MkInput from '../../../components/ui/input.vue'; export default Vue.extend({ - i18n, - components: { XContainer, MkInput }, diff --git a/src/client/pages/page-editor/els/page-editor.el.counter.vue b/src/client/pages/page-editor/els/page-editor.el.counter.vue index d9a4dddde..f439f3e6f 100644 --- a/src/client/pages/page-editor/els/page-editor.el.counter.vue +++ b/src/client/pages/page-editor/els/page-editor.el.counter.vue @@ -13,13 +13,10 @@ <script lang="ts"> import Vue from 'vue'; import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; import MkInput from '../../../components/ui/input.vue'; export default Vue.extend({ - i18n, - components: { XContainer, MkInput }, diff --git a/src/client/pages/page-editor/els/page-editor.el.if.vue b/src/client/pages/page-editor/els/page-editor.el.if.vue index 0449b9cf2..53cb9e2ae 100644 --- a/src/client/pages/page-editor/els/page-editor.el.if.vue +++ b/src/client/pages/page-editor/els/page-editor.el.if.vue @@ -28,13 +28,10 @@ import Vue from 'vue'; import { v4 as uuid } from 'uuid'; import { faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; import MkSelect from '../../../components/ui/select.vue'; export default Vue.extend({ - i18n, - components: { XContainer, MkSelect }, diff --git a/src/client/pages/page-editor/els/page-editor.el.image.vue b/src/client/pages/page-editor/els/page-editor.el.image.vue index e22701e5c..dd690da6f 100644 --- a/src/client/pages/page-editor/els/page-editor.el.image.vue +++ b/src/client/pages/page-editor/els/page-editor.el.image.vue @@ -17,14 +17,11 @@ import Vue from 'vue'; import { faPencilAlt } from '@fortawesome/free-solid-svg-icons'; import { faImage, faFolderOpen } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; import MkFileThumbnail from '../../../components/drive-file-thumbnail.vue'; import { selectDriveFile } from '../../../scripts/select-drive-file'; export default Vue.extend({ - i18n, - components: { XContainer, MkFileThumbnail }, diff --git a/src/client/pages/page-editor/els/page-editor.el.number-input.vue b/src/client/pages/page-editor/els/page-editor.el.number-input.vue index 76dd25446..62d2e1bf8 100644 --- a/src/client/pages/page-editor/els/page-editor.el.number-input.vue +++ b/src/client/pages/page-editor/els/page-editor.el.number-input.vue @@ -13,13 +13,10 @@ <script lang="ts"> import Vue from 'vue'; import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; import MkInput from '../../../components/ui/input.vue'; export default Vue.extend({ - i18n, - components: { XContainer, MkInput }, diff --git a/src/client/pages/page-editor/els/page-editor.el.post.vue b/src/client/pages/page-editor/els/page-editor.el.post.vue index 2c6ce24e9..06dea51c1 100644 --- a/src/client/pages/page-editor/els/page-editor.el.post.vue +++ b/src/client/pages/page-editor/els/page-editor.el.post.vue @@ -13,15 +13,12 @@ <script lang="ts"> import Vue from 'vue'; import { faPaperPlane } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; import MkTextarea from '../../../components/ui/textarea.vue'; import MkInput from '../../../components/ui/input.vue'; import MkSwitch from '../../../components/ui/switch.vue'; export default Vue.extend({ - i18n, - components: { XContainer, MkTextarea, MkInput, MkSwitch }, diff --git a/src/client/pages/page-editor/els/page-editor.el.radio-button.vue b/src/client/pages/page-editor/els/page-editor.el.radio-button.vue index 8d404ec0d..34a9366d6 100644 --- a/src/client/pages/page-editor/els/page-editor.el.radio-button.vue +++ b/src/client/pages/page-editor/els/page-editor.el.radio-button.vue @@ -14,13 +14,11 @@ <script lang="ts"> import Vue from 'vue'; import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; import MkTextarea from '../../../components/ui/textarea.vue'; import MkInput from '../../../components/ui/input.vue'; export default Vue.extend({ - i18n, components: { XContainer, MkTextarea, MkInput }, diff --git a/src/client/pages/page-editor/els/page-editor.el.section.vue b/src/client/pages/page-editor/els/page-editor.el.section.vue index a32cf9c75..e89a8b840 100644 --- a/src/client/pages/page-editor/els/page-editor.el.section.vue +++ b/src/client/pages/page-editor/els/page-editor.el.section.vue @@ -21,12 +21,9 @@ import Vue from 'vue'; import { v4 as uuid } from 'uuid'; import { faPlus, faPencilAlt } from '@fortawesome/free-solid-svg-icons'; import { faStickyNote } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; export default Vue.extend({ - i18n, - components: { XContainer }, diff --git a/src/client/pages/page-editor/els/page-editor.el.switch.vue b/src/client/pages/page-editor/els/page-editor.el.switch.vue index 8f169c3d2..5055da4f6 100644 --- a/src/client/pages/page-editor/els/page-editor.el.switch.vue +++ b/src/client/pages/page-editor/els/page-editor.el.switch.vue @@ -13,14 +13,11 @@ <script lang="ts"> import Vue from 'vue'; import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; import MkSwitch from '../../../components/ui/switch.vue'; import MkInput from '../../../components/ui/input.vue'; export default Vue.extend({ - i18n, - components: { XContainer, MkSwitch, MkInput }, diff --git a/src/client/pages/page-editor/els/page-editor.el.text-input.vue b/src/client/pages/page-editor/els/page-editor.el.text-input.vue index 7c9e3d6a0..bd5fb3761 100644 --- a/src/client/pages/page-editor/els/page-editor.el.text-input.vue +++ b/src/client/pages/page-editor/els/page-editor.el.text-input.vue @@ -13,13 +13,10 @@ <script lang="ts"> import Vue from 'vue'; import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; import MkInput from '../../../components/ui/input.vue'; export default Vue.extend({ - i18n, - components: { XContainer, MkInput }, diff --git a/src/client/pages/page-editor/els/page-editor.el.text.vue b/src/client/pages/page-editor/els/page-editor.el.text.vue index c6722236e..a50b1113b 100644 --- a/src/client/pages/page-editor/els/page-editor.el.text.vue +++ b/src/client/pages/page-editor/els/page-editor.el.text.vue @@ -11,12 +11,9 @@ <script lang="ts"> import Vue from 'vue'; import { faAlignLeft } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; export default Vue.extend({ - i18n, - components: { XContainer }, diff --git a/src/client/pages/page-editor/els/page-editor.el.textarea-input.vue b/src/client/pages/page-editor/els/page-editor.el.textarea-input.vue index 8081e706b..33c49c705 100644 --- a/src/client/pages/page-editor/els/page-editor.el.textarea-input.vue +++ b/src/client/pages/page-editor/els/page-editor.el.textarea-input.vue @@ -13,14 +13,11 @@ <script lang="ts"> import Vue from 'vue'; import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; import MkTextarea from '../../../components/ui/textarea.vue'; import MkInput from '../../../components/ui/input.vue'; export default Vue.extend({ - i18n, - components: { XContainer, MkTextarea, MkInput }, diff --git a/src/client/pages/page-editor/els/page-editor.el.textarea.vue b/src/client/pages/page-editor/els/page-editor.el.textarea.vue index d31da5dfa..e2e8848cc 100644 --- a/src/client/pages/page-editor/els/page-editor.el.textarea.vue +++ b/src/client/pages/page-editor/els/page-editor.el.textarea.vue @@ -11,12 +11,9 @@ <script lang="ts"> import Vue from 'vue'; import { faAlignLeft } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../../i18n'; import XContainer from '../page-editor.container.vue'; export default Vue.extend({ - i18n, - components: { XContainer }, diff --git a/src/client/pages/page-editor/page-editor.container.vue b/src/client/pages/page-editor/page-editor.container.vue index 5bc974467..3fa09f560 100644 --- a/src/client/pages/page-editor/page-editor.container.vue +++ b/src/client/pages/page-editor/page-editor.container.vue @@ -28,11 +28,8 @@ import Vue from 'vue'; import { faBars, faAngleUp, faAngleDown } from '@fortawesome/free-solid-svg-icons'; import { faTrashAlt } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../../i18n'; export default Vue.extend({ - i18n, - props: { expanded: { type: Boolean, diff --git a/src/client/pages/page-editor/page-editor.script-block.vue b/src/client/pages/page-editor/page-editor.script-block.vue index 9eafd5daa..f3270f02e 100644 --- a/src/client/pages/page-editor/page-editor.script-block.vue +++ b/src/client/pages/page-editor/page-editor.script-block.vue @@ -59,14 +59,11 @@ import Vue from 'vue'; import { faPencilAlt, faPlug } from '@fortawesome/free-solid-svg-icons'; import { v4 as uuid } from 'uuid'; -import i18n from '../../i18n'; import XContainer from './page-editor.container.vue'; import MkTextarea from '../../components/ui/textarea.vue'; import { isLiteralBlock, funcDefs, blockDefs } from '../../scripts/hpml/index'; export default Vue.extend({ - i18n, - components: { XContainer, MkTextarea }, diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue index 4437c7716..2beb2df38 100644 --- a/src/client/pages/page-editor/page-editor.vue +++ b/src/client/pages/page-editor/page-editor.vue @@ -91,7 +91,6 @@ import PrismEditor from 'vue-prism-editor'; import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons'; import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-icons'; import { v4 as uuid } from 'uuid'; -import i18n from '../../i18n'; import XVariable from './page-editor.script-block.vue'; import XBlocks from './page-editor.blocks.vue'; import MkTextarea from '../../components/ui/textarea.vue'; @@ -107,8 +106,6 @@ import { collectPageVars } from '../../scripts/collect-page-vars'; import { selectDriveFile } from '../../scripts/select-drive-file'; export default Vue.extend({ - i18n, - components: { XDraggable, XVariable, XBlocks, MkTextarea, MkContainer, MkButton, MkSelect, MkSwitch, MkInput, PrismEditor }, diff --git a/src/client/pages/pages.vue b/src/client/pages/pages.vue index dd3d09db0..a26178571 100644 --- a/src/client/pages/pages.vue +++ b/src/client/pages/pages.vue @@ -28,14 +28,12 @@ import Vue from 'vue'; import { faPlus, faEdit } from '@fortawesome/free-solid-svg-icons'; import { faStickyNote, faHeart } from '@fortawesome/free-regular-svg-icons'; -import i18n from '../i18n'; import MkPagePreview from '../components/page-preview.vue'; import MkPagination from '../components/ui/pagination.vue'; import MkButton from '../components/ui/button.vue'; import MkContainer from '../components/ui/container.vue'; export default Vue.extend({ - i18n, components: { MkPagePreview, MkPagination, MkButton, MkContainer }, diff --git a/src/client/pages/preferences/index.vue b/src/client/pages/preferences/index.vue index 9f4bb6795..14d22bf02 100644 --- a/src/client/pages/preferences/index.vue +++ b/src/client/pages/preferences/index.vue @@ -99,8 +99,8 @@ import MkRadio from '../../components/ui/radio.vue'; import MkRange from '../../components/ui/range.vue'; import XTheme from './theme.vue'; import XSidebar from './sidebar.vue'; -import i18n from '../../i18n'; import { langs } from '../../config'; +import { clientDb, set } from '../../db'; const sounds = [ null, @@ -120,8 +120,6 @@ const sounds = [ ]; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('settings') as string @@ -228,9 +226,23 @@ export default Vue.extend({ watch: { lang() { + const dialog = this.$root.dialog({ + type: 'waiting', + iconOnly: true + }); + localStorage.setItem('lang', this.lang); - localStorage.removeItem('locale'); - location.reload(); + + return set('_version_', `changeLang-${(new Date()).toJSON()}`, clientDb.i18n) + .then(() => location.reload()) + .catch(() => { + dialog.close(); + this.$root.dialog({ + type: 'error', + iconOnly: true, + autoClose: true + }); + }); }, fontSize() { diff --git a/src/client/pages/preferences/sidebar.vue b/src/client/pages/preferences/sidebar.vue index 2dced10e7..34c9916cf 100644 --- a/src/client/pages/preferences/sidebar.vue +++ b/src/client/pages/preferences/sidebar.vue @@ -19,12 +19,9 @@ import Vue from 'vue'; import { faListUl, faSave, faRedo } from '@fortawesome/free-solid-svg-icons'; import MkButton from '../../components/ui/button.vue'; import MkTextarea from '../../components/ui/textarea.vue'; -import i18n from '../../i18n'; import { defaultDeviceUserSettings } from '../../store'; export default Vue.extend({ - i18n, - components: { MkButton, MkTextarea, diff --git a/src/client/pages/preferences/theme.vue b/src/client/pages/preferences/theme.vue index f35b5d6ed..2111fa224 100644 --- a/src/client/pages/preferences/theme.vue +++ b/src/client/pages/preferences/theme.vue @@ -87,14 +87,11 @@ import MkButton from '../../components/ui/button.vue'; import MkSelect from '../../components/ui/select.vue'; import MkSwitch from '../../components/ui/switch.vue'; import MkTextarea from '../../components/ui/textarea.vue'; -import i18n from '../../i18n'; import { Theme, builtinThemes, applyTheme, validateTheme } from '../../theme'; import { selectFile } from '../../scripts/select-file'; import { isDeviceDarkmode } from '../../scripts/is-device-darkmode'; export default Vue.extend({ - i18n, - components: { MkInput, MkButton, diff --git a/src/client/pages/room/room.vue b/src/client/pages/room/room.vue index 6ede771c5..cf6850526 100644 --- a/src/client/pages/room/room.vue +++ b/src/client/pages/room/room.vue @@ -59,7 +59,6 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../../i18n'; import { Room } from '../../scripts/room/room'; import parseAcct from '../../../misc/acct/parse'; import XPreview from './preview.vue'; @@ -74,8 +73,6 @@ import { selectFile } from '../../scripts/select-file'; let room: Room; export default Vue.extend({ - i18n, - components: { XPreview, MkButton, diff --git a/src/client/pages/scratchpad.vue b/src/client/pages/scratchpad.vue index 622c40398..81d4e6045 100644 --- a/src/client/pages/scratchpad.vue +++ b/src/client/pages/scratchpad.vue @@ -28,14 +28,11 @@ import "prismjs"; import 'prismjs/themes/prism-okaidia.css'; import PrismEditor from 'vue-prism-editor'; import { AiScript, parse, utils, values } from '@syuilo/aiscript'; -import i18n from '../i18n'; import MkContainer from '../components/ui/container.vue'; import MkButton from '../components/ui/button.vue'; import { createAiScriptEnv } from '../scripts/create-aiscript-env'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('scratchpad') as string diff --git a/src/client/pages/share.vue b/src/client/pages/share.vue index 566650e30..153de7680 100644 --- a/src/client/pages/share.vue +++ b/src/client/pages/share.vue @@ -18,13 +18,10 @@ <script lang="ts"> import Vue from 'vue'; import { faShareAlt } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../i18n'; import PostFormDialog from '../components/post-form-dialog.vue'; import MkButton from '../components/ui/button.vue'; export default Vue.extend({ - i18n, - metaInfo() { return { title: this.$t('share') as string diff --git a/src/client/pages/user/follow-list.vue b/src/client/pages/user/follow-list.vue index 3da0b8359..666e2d04f 100644 --- a/src/client/pages/user/follow-list.vue +++ b/src/client/pages/user/follow-list.vue @@ -19,13 +19,10 @@ <script lang="ts"> import Vue from 'vue'; import parseAcct from '../../../misc/acct/parse'; -import i18n from '../../i18n'; import MkFollowButton from '../../components/follow-button.vue'; import MkPagination from '../../components/ui/pagination.vue'; export default Vue.extend({ - i18n, - components: { MkPagination, MkFollowButton, diff --git a/src/client/pages/user/index.photos.vue b/src/client/pages/user/index.photos.vue index 07b4db0a9..83a261840 100644 --- a/src/client/pages/user/index.photos.vue +++ b/src/client/pages/user/index.photos.vue @@ -14,11 +14,9 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../../i18n'; import { getStaticImageUrl } from '../../scripts/get-static-image-url'; export default Vue.extend({ - i18n, props: ['user'], data() { return { diff --git a/src/client/scripts/compose-notification.ts b/src/client/scripts/compose-notification.ts index bf3255250..29eb515bf 100644 --- a/src/client/scripts/compose-notification.ts +++ b/src/client/scripts/compose-notification.ts @@ -1,58 +1,95 @@ import getNoteSummary from '../../misc/get-note-summary'; import getUserName from '../../misc/get-user-name'; +import { clientDb, get, bulkGet } from '../db'; +import { fromEntries } from '../../prelude/array'; -type Notification = { - title: string; - body: string; - icon: string; - onclick?: any; -}; +const getTranslation = (text: string): Promise<string> => get(text, clientDb.i18n); -// TODO: i18n +export default async function(type, data): Promise<[string, NotificationOptions]> { + const contexts = ['deletedNote', 'invisibleNote', 'withNFiles', '_cw.poll']; + const locale = fromEntries(await bulkGet(contexts, clientDb.i18n) as [string, string][]); -export default function(type, data): Notification { switch (type) { - case 'driveFileCreated': - return { - title: 'File uploaded', + case 'driveFileCreated': // TODO (Server Side) + return [await getTranslation('_notification.fileUploaded'), { body: data.name, icon: data.url - }; - + }]; case 'notification': switch (data.type) { case 'mention': - return { - title: `${getUserName(data.user)}:`, - body: getNoteSummary(data), + return [(await getTranslation('_notification.youGotMention')).replace('{name}', getUserName(data.user)), { + body: getNoteSummary(data.note, locale), icon: data.user.avatarUrl - }; + }]; case 'reply': - return { - title: `You got reply from ${getUserName(data.user)}:`, - body: getNoteSummary(data), + return [(await getTranslation('_notification.youGotReply')).replace('{name}', getUserName(data.user)), { + body: getNoteSummary(data.note, locale), icon: data.user.avatarUrl - }; + }]; + + case 'renote': + return [(await getTranslation('_notification.youRenoted')).replace('{name}', getUserName(data.user)), { + body: getNoteSummary(data.note, locale), + icon: data.user.avatarUrl + }]; case 'quote': - return { - title: `${getUserName(data.user)}:`, - body: getNoteSummary(data), + return [(await getTranslation('_notification.youGotQuote')).replace('{name}', getUserName(data.user)), { + body: getNoteSummary(data.note, locale), icon: data.user.avatarUrl - }; + }]; case 'reaction': - return { - title: `${getUserName(data.user)}: ${data.reaction}:`, - body: getNoteSummary(data.note), + return [`${data.reaction} ${getUserName(data.user)}`, { + body: getNoteSummary(data.note, locale), icon: data.user.avatarUrl - }; + }]; + + case 'pollVote': + return [(await getTranslation('_notification.youGotPoll')).replace('{name}', getUserName(data.user)), { + body: getNoteSummary(data.note, locale), + icon: data.user.avatarUrl + }]; + + case 'follow': + return [await getTranslation('_notification.youWereFollowed'), { + body: getUserName(data.user), + icon: data.user.avatarUrl + }]; + + case 'receiveFollowRequest': + return [await getTranslation('_notification.youReceivedFollowRequest'), { + body: getUserName(data.user), + icon: data.user.avatarUrl + }]; + + case 'followRequestAccepted': + return [await getTranslation('_notification.yourFollowRequestAccepted'), { + body: getUserName(data.user), + icon: data.user.avatarUrl + }]; + + case 'groupInvited': + return [await getTranslation('_notification.youWereInvitedToGroup'), { + body: data.group.name + }]; default: return null; } - + case 'unreadMessagingMessage': + if (data.groupId === null) { + return [(await getTranslation('_notification.youGotMessagingMessageFromUser')).replace('{name}', getUserName(data.user)), { + icon: data.user.avatarUrl, + tag: `messaging:user:${data.user.id}` + }]; + } + return [(await getTranslation('_notification.youGotMessagingMessageFromGroup')).replace('{name}', data.group.name), { + icon: data.user.avatarUrl, + tag: `messaging:group:${data.group.id}` + }]; default: return null; } diff --git a/src/client/scripts/set-i18n-contexts.ts b/src/client/scripts/set-i18n-contexts.ts new file mode 100644 index 000000000..2eb76047f --- /dev/null +++ b/src/client/scripts/set-i18n-contexts.ts @@ -0,0 +1,18 @@ +import VueI18n from 'vue-i18n'; +import { clientDb, clear, bulkSet } from '../db'; +import { deepEntries, delimitEntry } from 'deep-entries'; +import { fromEntries } from '../../prelude/array'; + +export function setI18nContexts(lang: string, version: string, i18n: VueI18n, cleardb = false) { + return Promise.all([ + cleardb ? clear(clientDb.i18n) : Promise.resolve(), + fetch(`/assets/locales/${lang}.${version}.json`) + ]) + .then(([, response]) => response.json()) + .then(locale => { + const flatLocaleEntries = deepEntries(locale, delimitEntry) as [string, string][]; + bulkSet(flatLocaleEntries, clientDb.i18n); + i18n.locale = lang; + i18n.setLocaleMessage(lang, fromEntries(flatLocaleEntries)); + }); +} diff --git a/src/client/sw.js b/src/client/sw.ts similarity index 88% rename from src/client/sw.js rename to src/client/sw.ts index 68e43429a..341198852 100644 --- a/src/client/sw.js +++ b/src/client/sw.ts @@ -1,6 +1,7 @@ /** * Service Worker */ +declare var self: ServiceWorkerGlobalScope; import composeNotification from './scripts/compose-notification'; @@ -14,7 +15,7 @@ const apiUrl = `${location.origin}/api/`; self.addEventListener('install', ev => { console.info('installed'); - ev.waitUntil( + ev.waitUntil( caches.open(cacheName) .then(cache => { return cache.addAll([ @@ -22,7 +23,7 @@ self.addEventListener('install', ev => { ]); }) .then(() => self.skipWaiting()) - ); + ); }); self.addEventListener('activate', ev => { @@ -55,16 +56,12 @@ self.addEventListener('push', ev => { // クライアント取得 ev.waitUntil(self.clients.matchAll({ includeUncontrolled: true - }).then(clients => { + }).then(async clients => { // クライアントがあったらストリームに接続しているということなので通知しない if (clients.length != 0) return; const { type, body } = ev.data.json(); - const n = composeNotification(type, body); - return self.registration.showNotification(n.title, { - body: n.body, - icon: n.icon, - }); + return self.registration.showNotification(...(await composeNotification(type, body))); })); }); diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json index 3ec0271f6..aac0d1bfe 100644 --- a/src/client/tsconfig.json +++ b/src/client/tsconfig.json @@ -21,6 +21,11 @@ "typeRoots": [ "node_modules/@types", "src/@types" + ], + "lib": [ + "esnext", + "dom", + "webworker" ] }, "compileOnSave": false, diff --git a/src/client/widgets/activity.chart.vue b/src/client/widgets/activity.chart.vue index 0278e02ae..2b7049355 100644 --- a/src/client/widgets/activity.chart.vue +++ b/src/client/widgets/activity.chart.vue @@ -26,7 +26,6 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../i18n'; function dragListen(fn) { window.addEventListener('mousemove', fn); @@ -41,7 +40,6 @@ function dragClear(fn) { } export default Vue.extend({ - i18n, props: ['data'], data() { return { diff --git a/src/client/widgets/activity.vue b/src/client/widgets/activity.vue index 6c32642bb..4fdd81ae5 100644 --- a/src/client/widgets/activity.vue +++ b/src/client/widgets/activity.vue @@ -19,7 +19,6 @@ import { faChartBar, faSort } from '@fortawesome/free-solid-svg-icons'; import MkContainer from '../components/ui/container.vue'; import define from './define'; -import i18n from '../i18n'; import XCalendar from './activity.calendar.vue'; import XChart from './activity.chart.vue'; @@ -30,7 +29,6 @@ export default define({ view: 0 }) }).extend({ - i18n, components: { MkContainer, XCalendar, diff --git a/src/client/widgets/calendar.vue b/src/client/widgets/calendar.vue index c041734a4..328e6bc62 100644 --- a/src/client/widgets/calendar.vue +++ b/src/client/widgets/calendar.vue @@ -33,7 +33,6 @@ <script lang="ts"> import define from './define'; -import i18n from '../i18n'; export default define({ name: 'calendar', @@ -41,7 +40,6 @@ export default define({ design: 0 }) }).extend({ - i18n, data() { return { now: new Date(), diff --git a/src/client/widgets/memo.vue b/src/client/widgets/memo.vue index 3c170adc4..cdc716b9f 100644 --- a/src/client/widgets/memo.vue +++ b/src/client/widgets/memo.vue @@ -15,7 +15,6 @@ import { faStickyNote } from '@fortawesome/free-solid-svg-icons'; import MkContainer from '../components/ui/container.vue'; import define from './define'; -import i18n from '../i18n'; export default define({ name: 'memo', @@ -23,7 +22,6 @@ export default define({ compact: false }) }).extend({ - i18n, components: { MkContainer diff --git a/src/client/widgets/notifications.vue b/src/client/widgets/notifications.vue index 9c1bddb2e..39fc8a936 100644 --- a/src/client/widgets/notifications.vue +++ b/src/client/widgets/notifications.vue @@ -15,7 +15,6 @@ import { faBell } from '@fortawesome/free-solid-svg-icons'; import MkContainer from '../components/ui/container.vue'; import XNotifications from '../components/notifications.vue'; import define from './define'; -import i18n from '../i18n'; const basisSteps = [25, 50, 75, 100] const previewHeights = [200, 300, 400, 500] @@ -27,7 +26,6 @@ export default define({ basisStep: 0 }) }).extend({ - i18n, components: { MkContainer, diff --git a/src/client/widgets/photos.vue b/src/client/widgets/photos.vue index 1deb6de62..6e4e43a56 100644 --- a/src/client/widgets/photos.vue +++ b/src/client/widgets/photos.vue @@ -20,7 +20,6 @@ import { faCamera } from '@fortawesome/free-solid-svg-icons'; import MkContainer from '../components/ui/container.vue'; import define from './define'; -import i18n from '../i18n'; import { getStaticImageUrl } from '../scripts/get-static-image-url'; export default define({ @@ -29,7 +28,6 @@ export default define({ design: 0, }) }).extend({ - i18n, components: { MkContainer, }, diff --git a/src/client/widgets/rss.vue b/src/client/widgets/rss.vue index 61c1e23b6..4e57281e9 100644 --- a/src/client/widgets/rss.vue +++ b/src/client/widgets/rss.vue @@ -18,7 +18,6 @@ import { faRssSquare, faCog } from '@fortawesome/free-solid-svg-icons'; import MkContainer from '../components/ui/container.vue'; import define from './define'; -import i18n from '../i18n'; export default define({ name: 'rss', @@ -27,7 +26,6 @@ export default define({ url: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews' }) }).extend({ - i18n, components: { MkContainer }, diff --git a/src/client/widgets/timeline.vue b/src/client/widgets/timeline.vue index 55f78f985..633131182 100644 --- a/src/client/widgets/timeline.vue +++ b/src/client/widgets/timeline.vue @@ -27,7 +27,6 @@ import { faComments } from '@fortawesome/free-regular-svg-icons'; import MkContainer from '../components/ui/container.vue'; import XTimeline from '../components/timeline.vue'; import define from './define'; -import i18n from '../i18n'; const basisSteps = [25, 50, 75, 100] const previewHeights = [200, 300, 400, 500] @@ -41,7 +40,6 @@ export default define({ basisStep: 0 }) }).extend({ - i18n, components: { MkContainer, diff --git a/src/client/widgets/trends.vue b/src/client/widgets/trends.vue index 690383d1f..61f5bfbd3 100644 --- a/src/client/widgets/trends.vue +++ b/src/client/widgets/trends.vue @@ -23,7 +23,6 @@ import { faHashtag } from '@fortawesome/free-solid-svg-icons'; import MkContainer from '../components/ui/container.vue'; import define from './define'; -import i18n from '../i18n'; import XChart from './trends.chart.vue'; export default define({ @@ -32,7 +31,6 @@ export default define({ compact: false }) }).extend({ - i18n, components: { MkContainer, XChart }, diff --git a/src/misc/get-note-summary.ts b/src/misc/get-note-summary.ts index e3458cb18..c23306ab1 100644 --- a/src/misc/get-note-summary.ts +++ b/src/misc/get-note-summary.ts @@ -2,13 +2,13 @@ * 投稿を表す文字列を取得します。 * @param {*} note (packされた)投稿 */ -const summarize = (note: any): string => { +const summarize = (note: any, locale: any): string => { if (note.deletedAt) { - return '(削除された投稿)'; + return `(${locale['deletedNote']})`; } if (note.isHidden) { - return '(非公開の投稿)'; + return `(${locale['invisibleNote']})`; } let summary = ''; @@ -22,18 +22,18 @@ const summarize = (note: any): string => { // ファイルが添付されているとき if ((note.files || []).length != 0) { - summary += ` (${note.files.length}つのファイル)`; + summary += ` (${locale['withNFiles'].replace('{n}', note.files.length)})`; } // 投票が添付されているとき if (note.poll) { - summary += ' (投票)'; + summary += ` (${locale._cw?.poll || locale['_cw.poll']})`; } // 返信のとき if (note.replyId) { if (note.reply) { - summary += `\n\nRE: ${summarize(note.reply)}`; + summary += `\n\nRE: ${summarize(note.reply, locale)}`; } else { summary += '\n\nRE: ...'; } @@ -42,7 +42,7 @@ const summarize = (note: any): string => { // Renoteのとき if (note.renoteId) { if (note.renote) { - summary += `\n\nRN: ${summarize(note.renote)}`; + summary += `\n\nRN: ${summarize(note.renote, locale)}`; } else { summary += '\n\nRN: ...'; } diff --git a/src/prelude/array.ts b/src/prelude/array.ts index f4d684d57..9e1dfead5 100644 --- a/src/prelude/array.ts +++ b/src/prelude/array.ts @@ -130,7 +130,17 @@ export function cumulativeSum(xs: number[]): number[] { } // Object.fromEntries() -export function fromEntries(xs: [string, any][]): { [x: string]: any; } { +export function fromEntries<T extends readonly (readonly [PropertyKey, any])[]>(xs: T): + T[number] extends infer U + ? + ( + U extends readonly any[] + ? (x: { [_ in U[0]]: U[1] }) => any + : never + ) extends (x: infer V) => any + ? V + : never + : never { return xs.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {} as { [x: string]: any; }); } diff --git a/src/server/web/index.ts b/src/server/web/index.ts index 3da86944d..5bb052a69 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -245,7 +245,8 @@ router.get('/notes/:note', async ctx => { const meta = await fetchMeta(); await ctx.render('note', { note: _note, - summary: getNoteSummary(_note), + // TODO: Let locale changeable by instance setting + summary: getNoteSummary(_note, locales['ja-JP']), instanceName: meta.name || 'Misskey', icon: meta.iconUrl }); diff --git a/src/server/web/views/flush.pug b/src/server/web/views/flush.pug index f279c2360..59fed1f15 100644 --- a/src/server/web/views/flush.pug +++ b/src/server/web/views/flush.pug @@ -1,20 +1,38 @@ doctype html html + #msg script. - localStorage.removeItem('locale'); + const msg = document.getElementById('msg'); try { - navigator.serviceWorker.controller.postMessage('clear'); + localStorage.clear(); + message('localStorage cleared'); - navigator.serviceWorker.getRegistrations().then(registrations => { - return Promise.all(registrations.map(registration => registration.unregister())); - }).then(() => { - location = '/'; - }); + const delidb = indexedDB.deleteDatabase('MisskeyClient'); + delidb.onsuccess = () => message('indexedDB cleared'); + + if (navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage('clear'); + navigator.serviceWorker.getRegistrations() + .then(registrations => { + return Promise.all(registrations.map(registration => registration.unregister())); + }) + .then(() => { + message('Success Flush! Please reopen Misskey.\n成功しました。Misskeyを開き直してください。'); + }) + .catch(e => { throw Error(e) }); + } else { + message('Success Flush! Please reopen Misskey.\n成功しました。Misskeyを開き直してください。'); + } } catch (e) { console.error(e); + message(`${e}¥n¥nFlush Failed. Please reopen Misskey.\n失敗しました。Misskeyを開き直してください。`); setTimeout(() => { location = '/'; }, 10000) } + + function message(text) { + msg.insertAdjacentHTML('beforeend', `<p>[${(new Date()).toString()}] ${text.replace(/¥n/g,'<br>')}</p>`) + } diff --git a/src/services/push-notification.ts b/src/services/push-notification.ts index f0d9c4e22..d0a0c04d6 100644 --- a/src/services/push-notification.ts +++ b/src/services/push-notification.ts @@ -2,8 +2,13 @@ import * as push from 'web-push'; import config from '../config'; import { SwSubscriptions } from '../models'; import { fetchMeta } from '../misc/fetch-meta'; +import { PackedNotification } from '../models/repositories/notification'; +import { PackedMessagingMessage } from '../models/repositories/messaging-message'; -export default async function(userId: string, type: string, body?: any) { +type notificationType = 'notification' | 'unreadMessagingMessage'; +type notificationBody = PackedNotification | PackedMessagingMessage; + +export default async function(userId: string, type: notificationType, body: notificationBody) { const meta = await fetchMeta(); if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return; diff --git a/webpack.config.ts b/webpack.config.ts index 64cf4c858..fa364e603 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -34,7 +34,7 @@ const postcss = { module.exports = { entry: { app: './src/client/init.ts', - sw: './src/client/sw.js' + sw: './src/client/sw.ts' }, module: { rules: [{ diff --git a/yarn.lock b/yarn.lock index c10ddb19c..4ea29f672 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2819,6 +2819,11 @@ decompress-response@^4.2.0: dependencies: mimic-response "^2.0.0" +deep-entries@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/deep-entries/-/deep-entries-3.1.0.tgz#e456aa791d01b045641c75e41e170c0c95a9d472" + integrity sha512-pCpcCqx/hclnT2e4mMlM9geG8XIaxWN+yNKJHHwu1FZyYKErKU/fPztYYSk2HwnqRPf55cDEXraV6MLv8I5FrA== + deep-eql@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" @@ -4488,6 +4493,11 @@ icss-utils@^4.0.0, icss-utils@^4.1.1: dependencies: postcss "^7.0.14" +idb-keyval@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-3.2.0.tgz#cbbf354deb5684b6cdc84376294fc05932845bd6" + integrity sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ== + ieee754@1.1.13, ieee754@^1.1.13, ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"