summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2020-12-26 10:01:32 +0900
committersyuilo <syuilotan@yahoo.co.jp>2020-12-26 10:01:32 +0900
commit9d81d068533aaddf8e8654f9e86374c6531766bb (patch)
tree2b6ff1106eff6431a2562e7e1fd8fd2531000045 /src/client
parentShowusers order by updateAt NULL considered as max (#7015) (diff)
downloadmisskey-9d81d068533aaddf8e8654f9e86374c6531766bb.tar.gz
misskey-9d81d068533aaddf8e8654f9e86374c6531766bb.tar.bz2
misskey-9d81d068533aaddf8e8654f9e86374c6531766bb.zip
wip
Diffstat (limited to 'src/client')
-rw-r--r--src/client/.eslintrc2
-rw-r--r--src/client/@types/global.d.ts4
-rw-r--r--src/client/components/abuse-report-window.vue4
-rw-r--r--src/client/components/channel-preview.vue8
-rw-r--r--src/client/components/global/i18n.ts15
-rw-r--r--src/client/components/index.ts2
-rw-r--r--src/client/components/note.vue8
-rw-r--r--src/client/components/signup.vue4
-rw-r--r--src/client/config.ts6
-rw-r--r--src/client/i18n.ts67
-rw-r--r--src/client/init.ts9
-rw-r--r--src/client/pages/channel.vue4
-rw-r--r--src/client/pages/reversi/game.setting.vue8
-rw-r--r--src/client/pages/reversi/index.vue4
-rw-r--r--src/client/pages/settings/2fa.vue4
-rw-r--r--src/client/pages/settings/general.vue4
-rw-r--r--src/client/pages/timeline.tutorial.vue8
-rw-r--r--src/client/scripts/set-i18n-contexts.ts15
-rw-r--r--src/client/ui/deck.vue2
19 files changed, 99 insertions, 79 deletions
diff --git a/src/client/.eslintrc b/src/client/.eslintrc
index 8829472b49..5e309a95c1 100644
--- a/src/client/.eslintrc
+++ b/src/client/.eslintrc
@@ -1,7 +1,9 @@
{
"globals": {
"_DEV_": false,
+ "_LANG_": false,
"_LANGS_": false,
+ "_LOCALE_": false,
"_VERSION_": false,
"_ENV_": false,
"_PERF_PREFIX_": false,
diff --git a/src/client/@types/global.d.ts b/src/client/@types/global.d.ts
index 670774fdf4..a1ce02735a 100644
--- a/src/client/@types/global.d.ts
+++ b/src/client/@types/global.d.ts
@@ -1,4 +1,6 @@
-declare const _LANGS_: string[];
+declare const _LANG_: string;
+declare const _LANGS_: string[][];
+declare const _LOCALE_: Record<string, any>;
declare const _VERSION_: string;
declare const _ENV_: string;
declare const _DEV_: boolean;
diff --git a/src/client/components/abuse-report-window.vue b/src/client/components/abuse-report-window.vue
index 1d87cb1802..c550e1e85d 100644
--- a/src/client/components/abuse-report-window.vue
+++ b/src/client/components/abuse-report-window.vue
@@ -2,11 +2,11 @@
<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="$emit('closed')">
<template #header>
<Fa :icon="faExclamationCircle" style="margin-right: 0.5em;"/>
- <i18n-t keypath="reportAbuseOf" tag="span">
+ <I18n src="reportAbuseOf" tag="span">
<template #name>
<b><MkAcct :user="user"/></b>
</template>
- </i18n-t>
+ </I18n>
</template>
<div class="dpvffvvy">
<div class="_section">
diff --git a/src/client/components/channel-preview.vue b/src/client/components/channel-preview.vue
index 41be1c296d..241fbe4f68 100644
--- a/src/client/components/channel-preview.vue
+++ b/src/client/components/channel-preview.vue
@@ -6,19 +6,19 @@
<div class="status">
<div>
<Fa :icon="faUsers" fixed-width/>
- <i18n-t keypath="_channel.usersCount" tag="span" style="margin-left: 4px;">
+ <I18n src="_channel.usersCount" tag="span" style="margin-left: 4px;">
<template #n>
<b>{{ channel.usersCount }}</b>
</template>
- </i18n-t>
+ </I18n>
</div>
<div>
<Fa :icon="faPencilAlt" fixed-width/>
- <i18n-t keypath="_channel.notesCount" tag="span" style="margin-left: 4px;">
+ <I18n src="_channel.notesCount" tag="span" style="margin-left: 4px;">
<template #n>
<b>{{ channel.notesCount }}</b>
</template>
- </i18n-t>
+ </I18n>
</div>
</div>
</div>
diff --git a/src/client/components/global/i18n.ts b/src/client/components/global/i18n.ts
new file mode 100644
index 0000000000..603c07ca97
--- /dev/null
+++ b/src/client/components/global/i18n.ts
@@ -0,0 +1,15 @@
+import { h, Fragment, defineComponent } from 'vue';
+import type { SetupContext, VNodeChild, RenderFunction } from 'vue';
+
+export default defineComponent({
+ props: {
+ src: {
+ type: String,
+ required: true
+ },
+ },
+ render() {
+ // TODO
+ return h('span', this.src);
+ }
+});
diff --git a/src/client/components/index.ts b/src/client/components/index.ts
index cccb5d766a..0630ed3d8c 100644
--- a/src/client/components/index.ts
+++ b/src/client/components/index.ts
@@ -9,6 +9,7 @@ import userName from './global/user-name.vue';
import ellipsis from './global/ellipsis.vue';
import time from './global/time.vue';
import url from './global/url.vue';
+import i18n from './global/i18n';
import loading from './global/loading.vue';
import error from './global/error.vue';
@@ -24,4 +25,5 @@ export default function(app: App) {
app.component('MkUrl', url);
app.component('MkLoading', loading);
app.component('MkError', error);
+ app.component('I18n', i18n);
}
diff --git a/src/client/components/note.vue b/src/client/components/note.vue
index 04696b7cfa..6d5750451e 100644
--- a/src/client/components/note.vue
+++ b/src/client/components/note.vue
@@ -16,13 +16,13 @@
<div class="renote" v-if="isRenote">
<MkAvatar class="avatar" :user="note.user"/>
<Fa :icon="faRetweet"/>
- <i18n-t keypath="renotedBy" tag="span">
+ <I18n src="renotedBy" tag="span">
<template #user>
<MkA class="name" :to="userPage(note.user)" v-user-preview="note.userId">
<MkUserName :user="note.user"/>
</MkA>
</template>
- </i18n-t>
+ </I18n>
<div class="info">
<button class="_button time" @click="showRenoteMenu()" ref="renoteTime">
<Fa class="dropdownIcon" v-if="isMyRenote" :icon="faEllipsisH"/>
@@ -90,13 +90,13 @@
<XSub v-for="note in replies" :key="note.id" :note="note" class="reply" :detail="true"/>
</div>
<div v-else class="_panel muted" @click="muted = false">
- <i18n-t keypath="userSaysSomething" tag="small">
+ <I18n src="userSaysSomething" tag="small">
<template #name>
<MkA class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId">
<MkUserName :user="appearNote.user"/>
</MkA>
</template>
- </i18n-t>
+ </I18n>
</div>
</template>
diff --git a/src/client/components/signup.vue b/src/client/components/signup.vue
index ec631c9429..5378ec38f4 100644
--- a/src/client/components/signup.vue
+++ b/src/client/components/signup.vue
@@ -38,9 +38,9 @@
</MkInput>
<label v-if="meta.tosUrl" class="tou">
<input type="checkbox" v-model="ToSAgreement">
- <i18n-t keypath="agreeTo">
+ <I18n src="agreeTo">
<a :href="meta.tosUrl" class="_link" target="_blank">{{ $t('tos') }}</a>
- </i18n-t>
+ </I18n>
</label>
<captcha v-if="meta.enableHcaptcha" class="captcha" provider="hcaptcha" ref="hcaptcha" v-model:value="hCaptchaResponse" :sitekey="meta.hcaptchaSiteKey"/>
<captcha v-if="meta.enableRecaptcha" class="captcha" provider="grecaptcha" ref="recaptcha" v-model:value="reCaptchaResponse" :sitekey="meta.recaptchaSiteKey"/>
diff --git a/src/client/config.ts b/src/client/config.ts
index d0b74be042..b9cc361cf6 100644
--- a/src/client/config.ts
+++ b/src/client/config.ts
@@ -1,5 +1,3 @@
-import { clientDb, entries } from './db';
-
const address = new URL(location.href);
const siteName = (document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement)?.content;
@@ -8,9 +6,9 @@ export const hostname = address.hostname;
export const url = address.origin;
export const apiUrl = url + '/api';
export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://') + '/streaming';
-export const lang = localStorage.getItem('lang');
+export const lang = _LANG_;
export const langs = _LANGS_;
-export const getLocale = async () => Object.fromEntries((await entries(clientDb.i18n)) as [string, string][]);
+export const locale = _LOCALE_;
export const version = _VERSION_;
export const instanceName = siteName === 'Misskey' ? host : siteName;
export const ui = localStorage.getItem('ui');
diff --git a/src/client/i18n.ts b/src/client/i18n.ts
index 6f2dd1a7d2..ee09c4da35 100644
--- a/src/client/i18n.ts
+++ b/src/client/i18n.ts
@@ -1,36 +1,49 @@
-import { createI18n } from 'vue-i18n';
-import { clientDb, get, count } from './db';
-import { setI18nContexts } from '@/scripts/set-i18n-contexts';
-import { version, langs, getLocale } from '@/config';
+import { markRaw } from 'vue';
+import { locale } from '@/config';
-let _lang = localStorage.getItem('lang');
+export class I18n<T extends Record<string, any>> {
+ public locale: T;
-if (_lang == null) {
- if (langs.map(x => x[0]).includes(navigator.language)) {
- _lang = navigator.language;
- } else {
- _lang = langs.map(x => x[0]).find(x => x.split('-')[0] == navigator.language);
+ constructor(locale: T) {
+ this.locale = locale;
- if (_lang == null) {
- // Fallback
- _lang = 'en-US';
+ if (_DEV_) {
+ console.log('i18n', this.locale);
}
- }
- localStorage.setItem('lang', _lang);
-}
+ //#region BIND
+ this.t = this.t.bind(this);
+ //#endregion
+ }
-export const lang = _lang;
+ // string にしているのは、ドット区切りでのパス指定を許可するため
+ // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
+ public t(key: string, args?: Record<string, any>): string {
+ try {
+ let str = key.split('.').reduce((o, i) => o[i], this.locale) as string;
+ if (args) {
+ for (const [k, v] of Object.entries(args)) {
+ str = str.replace(`{${k}}`, v);
+ }
+ }
+ return str;
+ } catch (e) {
+ if (_DEV_) {
+ console.warn(`missing localization '${key}'`);
+ return `⚠'${key}'⚠`;
+ }
-export const locale = await count(clientDb.i18n).then(async n => {
- if (n === 0) return await setI18nContexts(_lang, version);
- if ((await get('_version_', clientDb.i18n) !== version)) return await setI18nContexts(_lang, version, true);
+ return key;
+ }
+ }
+}
- return await getLocale();
-});
+export const i18n = markRaw(new I18n(locale));
-export const i18n = createI18n({
- sync: false,
- locale: _lang,
- messages: { [_lang]: locale }
-});
+// このファイルに書きたくないけどここに書かないと何故かVeturが認識しない
+declare module '@vue/runtime-core' {
+ interface ComponentCustomProperties {
+ $t: typeof i18n['t'];
+ $ts: typeof i18n['locale'];
+ }
+}
diff --git a/src/client/init.ts b/src/client/init.ts
index 609c1086a5..c5f147ca66 100644
--- a/src/client/init.ts
+++ b/src/client/init.ts
@@ -40,11 +40,11 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import widgets from '@/widgets';
import directives from '@/directives';
import components from '@/components';
-import { version, ui } from '@/config';
+import { version, ui, lang } from '@/config';
import { router } from '@/router';
import { applyTheme } from '@/scripts/theme';
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
-import { i18n, lang } from '@/i18n';
+import { i18n } from '@/i18n';
import { stream, isMobile, dialog } from '@/os';
import * as sound from '@/scripts/sound';
import { $i, refreshAccount, login, updateAccount, signout } from '@/account';
@@ -53,6 +53,8 @@ import { fetchInstance, instance } from '@/instance';
console.info(`Misskey v${version}`);
+window.clearTimeout(window.mkBootTimer);
+
if (_DEV_) {
console.warn('Development mode!!!');
@@ -175,10 +177,11 @@ app.config.globalProperties = {
$i,
$store: defaultStore,
$instance: instance,
+ $t: i18n.t,
+ $ts: i18n.locale,
};
app.use(router);
-app.use(i18n);
// eslint-disable-next-line vue/component-definition-name-casing
app.component('Fa', FontAwesomeIcon);
diff --git a/src/client/pages/channel.vue b/src/client/pages/channel.vue
index 790ad5230c..2610ab1002 100644
--- a/src/client/pages/channel.vue
+++ b/src/client/pages/channel.vue
@@ -10,8 +10,8 @@
</div>
<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" class="banner">
<div class="status">
- <div><Fa :icon="faUsers" fixed-width/><i18n-t keypath="_channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></i18n-t></div>
- <div><Fa :icon="faPencilAlt" fixed-width/><i18n-t keypath="_channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></i18n-t></div>
+ <div><Fa :icon="faUsers" fixed-width/><I18n src="_channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
+ <div><Fa :icon="faPencilAlt" fixed-width/><I18n src="_channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
</div>
<div class="fade"></div>
</div>
diff --git a/src/client/pages/reversi/game.setting.vue b/src/client/pages/reversi/game.setting.vue
index 0a98d880e7..f23a5816f2 100644
--- a/src/client/pages/reversi/game.setting.vue
+++ b/src/client/pages/reversi/game.setting.vue
@@ -35,18 +35,18 @@
<div>
<MkRadio v-model="game.bw" value="random" @update:modelValue="updateSettings('bw')">{{ $t('random') }}</MkRadio>
<MkRadio v-model="game.bw" :value="'1'" @update:modelValue="updateSettings('bw')">
- <i18n-t keypath="_reversi.blackIs" tag="span">
+ <I18n src="_reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user1"/></b>
</template>
- </i18n-t>
+ </I18n>
</MkRadio>
<MkRadio v-model="game.bw" :value="'2'" @update:modelValue="updateSettings('bw')">
- <i18n-t keypath="_reversi.blackIs" tag="span">
+ <I18n src="_reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user2"/></b>
</template>
- </i18n-t>
+ </I18n>
</MkRadio>
</div>
</div>
diff --git a/src/client/pages/reversi/index.vue b/src/client/pages/reversi/index.vue
index 0ae0ad5a04..d57b9ae20d 100644
--- a/src/client/pages/reversi/index.vue
+++ b/src/client/pages/reversi/index.vue
@@ -46,11 +46,11 @@
</div>
<div class="sazhgisb" v-else>
<h1>
- <i18n-t keypath="waitingFor" tag="span">
+ <I18n src="waitingFor" tag="span">
<template #x>
<b><MkUserName :user="matching"/></b>
</template>
- </i18n-t>
+ </I18n>
<MkEllipsis/>
</h1>
<div class="cancel">
diff --git a/src/client/pages/settings/2fa.vue b/src/client/pages/settings/2fa.vue
index 0e75382ee8..8d7a9a3832 100644
--- a/src/client/pages/settings/2fa.vue
+++ b/src/client/pages/settings/2fa.vue
@@ -45,14 +45,14 @@
<div v-if="data && !$i.twoFactorEnabled">
<ol style="margin: 0; padding: 0 0 0 1em;">
<li>
- <i18n-t keypath="_2fa.step1" tag="span">
+ <I18n src="_2fa.step1" tag="span">
<template #a>
<a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a>
</template>
<template #b>
<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" class="_link">Google Authenticator</a>
</template>
- </i18n-t>
+ </I18n>
</li>
<li>{{ $t('_2fa.step2') }}<br><img :src="data.qr"></li>
<li>{{ $t('_2fa.step3') }}<br>
diff --git a/src/client/pages/settings/general.vue b/src/client/pages/settings/general.vue
index 0151a29b5a..92622f4515 100644
--- a/src/client/pages/settings/general.vue
+++ b/src/client/pages/settings/general.vue
@@ -6,11 +6,11 @@
<template #label>{{ $t('uiLanguage') }}</template>
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
<template #caption>
- <i18n-t keypath="i18nInfo" tag="span">
+ <I18n src="i18nInfo" tag="span">
<template #link>
<MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink>
</template>
- </i18n-t>
+ </I18n>
</template>
</FormSelect>
diff --git a/src/client/pages/timeline.tutorial.vue b/src/client/pages/timeline.tutorial.vue
index f3347c1463..60e71b0979 100644
--- a/src/client/pages/timeline.tutorial.vue
+++ b/src/client/pages/timeline.tutorial.vue
@@ -23,14 +23,14 @@
</div>
<div class="_content" v-else-if="tutorial === 4">
<div>{{ $t('_tutorial.step5_1') }}</div>
- <i18n-t keypath="_tutorial.step5_2" tag="div">
+ <I18n src="_tutorial.step5_2" tag="div">
<template #featured>
<MkA class="_link" to="/featured">{{ $t('featured') }}</MkA>
</template>
<template #explore>
<MkA class="_link" to="/explore">{{ $t('explore') }}</MkA>
</template>
- </i18n-t>
+ </I18n>
<div>{{ $t('_tutorial.step5_3') }}</div>
<small>{{ $t('_tutorial.step5_4') }}</small>
</div>
@@ -41,11 +41,11 @@
</div>
<div class="_content" v-else-if="tutorial === 6">
<div>{{ $t('_tutorial.step7_1') }}</div>
- <i18n-t keypath="_tutorial.step7_2" tag="div">
+ <I18n src="_tutorial.step7_2" tag="div">
<template #help>
<MkA class="_link" to="/docs">{{ $t('help') }}</MkA>
</template>
- </i18n-t>
+ </I18n>
<div>{{ $t('_tutorial.step7_3') }}</div>
</div>
diff --git a/src/client/scripts/set-i18n-contexts.ts b/src/client/scripts/set-i18n-contexts.ts
deleted file mode 100644
index 6014957361..0000000000
--- a/src/client/scripts/set-i18n-contexts.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { clientDb, clear, bulkSet } from '../db';
-import { deepEntries, delimitEntry } from 'deep-entries';
-
-export function setI18nContexts(lang: string, version: string, 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);
- return Object.fromEntries(flatLocaleEntries);
- });
-}
diff --git a/src/client/ui/deck.vue b/src/client/ui/deck.vue
index 6d8fe4748e..a33e234107 100644
--- a/src/client/ui/deck.vue
+++ b/src/client/ui/deck.vue
@@ -177,7 +177,7 @@ export default defineComponent({
// TODO: この値を設定で変えられるようにする?
$columnMargin: 12px;
- $deckMargin: 12px;
+ $deckMargin: $columnMargin;
--margin: var(--marginHalf);