-
+
+
{{ i18n.ts.sensitive }}
{{ i18n.ts.clickToShow }}
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index f9dba0b15a..977c9020c7 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -4,68 +4,345 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
-
-
-
- {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}
- {{ defaultStore.state.dataSaver.media && video.size ? bytes(video.size) : i18n.ts.video }}
- {{ i18n.ts.clickToShow }}
-
-
-
-
diff --git a/packages/frontend/src/filters/hms.ts b/packages/frontend/src/filters/hms.ts
new file mode 100644
index 0000000000..7b5da965ff
--- /dev/null
+++ b/packages/frontend/src/filters/hms.ts
@@ -0,0 +1,65 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { i18n } from '@/i18n.js';
+
+export function hms(ms: number, options: {
+ textFormat?: 'colon' | 'locale';
+ enableSeconds?: boolean;
+ enableMs?: boolean;
+}) {
+ const _options = {
+ textFormat: 'colon',
+ enableSeconds: true,
+ enableMs: false,
+ ...options,
+ };
+
+ const res: {
+ h?: string;
+ m?: string;
+ s?: string;
+ ms?: string;
+ } = {};
+
+ // ミリ秒を秒に変換
+ let seconds = Math.floor(ms / 1000);
+
+ // 小数点以下の値(2位まで)
+ const mili = ms - seconds * 1000;
+
+ // 時間を計算
+ const hours = Math.floor(seconds / 3600);
+ res.h = format(hours);
+ seconds %= 3600;
+
+ // 分を計算
+ const minutes = Math.floor(seconds / 60);
+ res.m = format(minutes);
+ seconds %= 60;
+
+ // 残った秒数を取得
+ seconds = seconds % 60;
+ res.s = format(seconds);
+
+ // ミリ秒を取得
+ res.ms = format(Math.floor(mili / 10));
+
+ // 結果を返す
+ if (_options.textFormat === 'locale') {
+ res.h += i18n.ts._time.hour;
+ res.m += i18n.ts._time.minute;
+ res.s += i18n.ts._time.second;
+ }
+ return [
+ res.h.startsWith('00') ? undefined : res.h,
+ res.m,
+ (_options.enableSeconds ? res.s : undefined),
+ ].filter(v => v !== undefined).join(_options.textFormat === 'colon' ? ':' : ' ') + (_options.enableMs ? _options.textFormat === 'colon' ? `.${res.ms}` : ` ${res.ms}` : '');
+}
+
+function format(n: number) {
+ return n.toString().padStart(2, '0');
+}
diff --git a/packages/frontend/src/scripts/device-kind.ts b/packages/frontend/src/scripts/device-kind.ts
index 3843052a24..218eb718b1 100644
--- a/packages/frontend/src/scripts/device-kind.ts
+++ b/packages/frontend/src/scripts/device-kind.ts
@@ -11,6 +11,13 @@ const ua = navigator.userAgent.toLowerCase();
const isTablet = /ipad/.test(ua) || (/mobile|iphone|android/.test(ua) && window.innerWidth > 700);
const isSmartphone = !isTablet && /mobile|iphone|android/.test(ua);
+const isIPhone = /iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
+// navigator.platform may be deprecated but this check is still required
+const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
+const isIos = /ipad|iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
+
+export const isFullscreenNotSupported = isIPhone || isIos;
+
export const deviceKind: 'smartphone' | 'tablet' | 'desktop' = defaultStore.state.overridedDeviceKind ? defaultStore.state.overridedDeviceKind
: isSmartphone ? 'smartphone'
: isTablet ? 'tablet'
--
cgit v1.2.3-freya
From 945d6a2b094565b50bbe46739f7f9084ad703f59 Mon Sep 17 00:00:00 2001
From: syuilo
Date: Wed, 17 Jan 2024 20:11:32 +0900
Subject: enhance(drop-and-fusion): ゲームバランスの調整など
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../server/api/endpoints/bubble-game/register.ts | 4 +--
.../frontend/src/pages/drop-and-fusion.game.vue | 3 +-
packages/frontend/src/pages/drop-and-fusion.vue | 4 ++-
.../frontend/src/scripts/drop-and-fusion-engine.ts | 41 ++++++++++++++--------
4 files changed, 33 insertions(+), 19 deletions(-)
(limited to 'packages/frontend/src/scripts')
diff --git a/packages/backend/src/server/api/endpoints/bubble-game/register.ts b/packages/backend/src/server/api/endpoints/bubble-game/register.ts
index f092d16a70..8eb90fdbf9 100644
--- a/packages/backend/src/server/api/endpoints/bubble-game/register.ts
+++ b/packages/backend/src/server/api/endpoints/bubble-game/register.ts
@@ -63,8 +63,8 @@ export default class extends Endpoint { // eslint-
throw new ApiError(meta.errors.invalidSeed);
}
- // シードが古すぎる(1時間以上前)のも弾く
- if (seedDate.getTime() < now.getTime() - 1000 * 60 * 60) {
+ // シードが古すぎる(5時間以上前)のも弾く
+ if (seedDate.getTime() < now.getTime() - 1000 * 60 * 60 * 5) {
throw new ApiError(meta.errors.invalidSeed);
}
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index a8fa953c38..1fc0c7cd9c 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -496,7 +496,7 @@ const SWEETS_MONOS: FrontendMonoDefinition[] = [{
}];
const props = defineProps<{
- gameMode: 'normal' | 'square' | 'yen' | 'sweets';
+ gameMode: 'normal' | 'square' | 'yen' | 'sweets' | 'space';
mute: boolean;
}>();
@@ -509,6 +509,7 @@ const monoDefinitions = computed(() => {
props.gameMode === 'square' ? SQUARE_MONOS :
props.gameMode === 'yen' ? YEN_MONOS :
props.gameMode === 'sweets' ? SWEETS_MONOS :
+ props.gameMode === 'space' ? NORAML_MONOS :
[] as never;
});
diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue
index 18d3f56ca2..dd3b189c9d 100644
--- a/packages/frontend/src/pages/drop-and-fusion.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.vue
@@ -28,6 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
+
{{ i18n.ts.start }}
@@ -94,7 +95,7 @@ import MkSelect from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
-const gameMode = ref<'normal' | 'square' | 'yen' | 'sweets'>('normal');
+const gameMode = ref<'normal' | 'square' | 'yen' | 'sweets' | 'space'>('normal');
const gameStarted = ref(false);
const mute = ref(false);
const ranking = ref(null);
@@ -108,6 +109,7 @@ function getScoreUnit(gameMode: string) {
gameMode === 'square' ? 'pt' :
gameMode === 'yen' ? '円' :
gameMode === 'sweets' ? 'kcal' :
+ gameMode === 'space' ? 'pt' :
'' as never;
}
diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts
index 7c75822a20..b45aa591d1 100644
--- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts
+++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts
@@ -31,7 +31,7 @@ type Log = {
operation: 'surrender';
};
-const NORMAL_BASE_SIZE = 30;
+const NORMAL_BASE_SIZE = 32;
const NORAML_MONOS: Mono[] = [{
id: '9377076d-c980-4d83-bdaf-175bc58275b7',
level: 10,
@@ -114,7 +114,7 @@ const NORAML_MONOS: Mono[] = [{
dropCandidate: true,
}];
-const YEN_BASE_SIZE = 30;
+const YEN_BASE_SIZE = 32;
const YEN_SATSU_BASE_SIZE = 70;
const YEN_MONOS: Mono[] = [{
id: '880f9bd9-802f-4135-a7e1-fd0e0331f726',
@@ -1003,7 +1003,7 @@ export class DropAndFusionGame extends EventEmitter<{
private tickCallbackQueue: { frame: number; callback: () => void; }[] = [];
private overflowCollider: Matter.Body;
private isGameOver = false;
- private gameMode: 'normal' | 'yen' | 'square' | 'sweets';
+ private gameMode: 'normal' | 'yen' | 'square' | 'sweets' | 'space';
private rng: () => number;
private logs: Log[] = [];
@@ -1031,6 +1031,7 @@ export class DropAndFusionGame extends EventEmitter<{
case 'yen': return YEN_MONOS;
case 'square': return SQUARE_MONOS;
case 'sweets': return SWEETS_MONOS;
+ case 'space': return NORAML_MONOS;
}
}
@@ -1071,13 +1072,15 @@ export class DropAndFusionGame extends EventEmitter<{
this.getMonoRenderOptions = env.getMonoRenderOptions ?? null;
this.rng = seedrandom(env.seed);
+ // sweetsモードは重いため
+ const physicsQualityFactor = this.gameMode === 'sweets' ? 4 : this.PHYSICS_QUALITY_FACTOR;
this.engine = Matter.Engine.create({
- constraintIterations: 2 * this.PHYSICS_QUALITY_FACTOR,
- positionIterations: 6 * this.PHYSICS_QUALITY_FACTOR,
- velocityIterations: 4 * this.PHYSICS_QUALITY_FACTOR,
+ constraintIterations: 2 * physicsQualityFactor,
+ positionIterations: 6 * physicsQualityFactor,
+ velocityIterations: 4 * physicsQualityFactor,
gravity: {
x: 0,
- y: 1,
+ y: this.gameMode === 'space' ? 0.0125 : 1,
},
timing: {
timeScale: 2,
@@ -1092,7 +1095,7 @@ export class DropAndFusionGame extends EventEmitter<{
label: '_wall_',
isStatic: true,
friction: 0.7,
- slop: 1.0,
+ slop: this.gameMode === 'space' ? 0.01 : 0.7,
render: {
strokeStyle: 'transparent',
fillStyle: 'transparent',
@@ -1130,13 +1133,12 @@ export class DropAndFusionGame extends EventEmitter<{
private createBody(mono: Mono, x: number, y: number) {
const options: Matter.IBodyDefinition = {
label: mono.id,
- //density: 0.0005,
- density: ((mono.sizeX + mono.sizeY) / 2) / 1000,
- restitution: 0.2,
- frictionAir: 0.01,
- friction: 0.7,
- frictionStatic: 5,
- slop: 1.0,
+ density: this.gameMode === 'space' ? 0.01 : ((mono.sizeX * mono.sizeY) / 10000),
+ restitution: this.gameMode === 'space' ? 0.5 : 0.2,
+ frictionAir: this.gameMode === 'space' ? 0 : 0.01,
+ friction: this.gameMode === 'space' ? 0.5 : 0.7,
+ frictionStatic: this.gameMode === 'space' ? 0 : 5,
+ slop: this.gameMode === 'space' ? 0.01 : 0.7,
//mass: 0,
render: this.getMonoRenderOptions ? this.getMonoRenderOptions(mono) : undefined,
};
@@ -1327,6 +1329,15 @@ export class DropAndFusionGame extends EventEmitter<{
operation: 'drop',
x: inputX,
});
+
+ // add force
+ if (this.gameMode === 'space') {
+ Matter.Body.applyForce(body, body.position, {
+ x: 0,
+ y: (Math.PI * head.mono.sizeX * head.mono.sizeY) / 65536,
+ });
+ }
+
Matter.Composite.add(this.engine.world, body);
this.fusionReadyBodyIds.push(body.id);
--
cgit v1.2.3-freya
From 1dcf25c24f4a6ffd6fd8f9c0cc1c6e373d5a967b Mon Sep 17 00:00:00 2001
From: syuilo
Date: Thu, 18 Jan 2024 11:19:43 +0900
Subject: chore(drop-and-fusion): bump version
---
packages/frontend/src/scripts/drop-and-fusion-engine.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'packages/frontend/src/scripts')
diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts
index b45aa591d1..aef2613065 100644
--- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts
+++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts
@@ -990,7 +990,7 @@ export class DropAndFusionGame extends EventEmitter<{
}> {
private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる
private COMBO_INTERVAL = 60; // frame
- public readonly GAME_VERSION = 2;
+ public readonly GAME_VERSION = 3;
public readonly GAME_WIDTH = 450;
public readonly GAME_HEIGHT = 600;
public readonly DROP_COOLTIME = 30; // frame
--
cgit v1.2.3-freya
From 43401210c3691ffd8f8ac78b67bd5898e7981335 Mon Sep 17 00:00:00 2001
From: "Acid Chicken (硫酸鶏)"
Date: Fri, 19 Jan 2024 07:58:07 +0900
Subject: refactor: fully typed locales (#13033)
* refactor: fully typed locales
* refactor: hide parameterized locale strings from type data in ts access
* refactor: missing assertions
* docs: annotation
---
locales/generateDTS.js | 195 ++++++++++++++++++++++++-----
locales/index.d.ts | 229 ++++++++++++++++++----------------
packages/frontend/src/i18n.ts | 5 +-
packages/frontend/src/scripts/i18n.ts | 113 ++++++++++++++---
4 files changed, 380 insertions(+), 162 deletions(-)
(limited to 'packages/frontend/src/scripts')
diff --git a/locales/generateDTS.js b/locales/generateDTS.js
index d3afdd6e15..6eb5bd630d 100644
--- a/locales/generateDTS.js
+++ b/locales/generateDTS.js
@@ -6,54 +6,171 @@ import ts from 'typescript';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
+const parameterRegExp = /\{(\w+)\}/g;
+
+function createMemberType(item) {
+ if (typeof item !== 'string') {
+ return ts.factory.createTypeLiteralNode(createMembers(item));
+ }
+ const parameters = Array.from(
+ item.matchAll(parameterRegExp),
+ ([, parameter]) => parameter,
+ );
+ if (!parameters.length) {
+ return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
+ }
+ return ts.factory.createTypeReferenceNode(
+ ts.factory.createIdentifier('ParameterizedString'),
+ [
+ ts.factory.createUnionTypeNode(
+ parameters.map((parameter) =>
+ ts.factory.createLiteralTypeNode(
+ ts.factory.createStringLiteral(parameter),
+ ),
+ ),
+ ),
+ ],
+ );
+}
function createMembers(record) {
- return Object.entries(record)
- .map(([k, v]) => ts.factory.createPropertySignature(
+ return Object.entries(record).map(([k, v]) =>
+ ts.factory.createPropertySignature(
undefined,
ts.factory.createStringLiteral(k),
undefined,
- typeof v === 'string'
- ? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
- : ts.factory.createTypeLiteralNode(createMembers(v)),
- ));
+ createMemberType(v),
+ ),
+ );
}
export default function generateDTS() {
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
const members = createMembers(locale);
const elements = [
- ts.factory.createInterfaceDeclaration(
- [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createIdentifier('Locale'),
- undefined,
- undefined,
- members,
- ),
ts.factory.createVariableStatement(
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
ts.factory.createVariableDeclarationList(
- [ts.factory.createVariableDeclaration(
- ts.factory.createIdentifier('locales'),
+ [
+ ts.factory.createVariableDeclaration(
+ ts.factory.createIdentifier('kParameters'),
+ undefined,
+ ts.factory.createTypeOperatorNode(
+ ts.SyntaxKind.UniqueKeyword,
+ ts.factory.createKeywordTypeNode(ts.SyntaxKind.SymbolKeyword),
+ ),
+ undefined,
+ ),
+ ],
+ ts.NodeFlags.Const,
+ ),
+ ),
+ ts.factory.createInterfaceDeclaration(
+ [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
+ ts.factory.createIdentifier('ParameterizedString'),
+ [
+ ts.factory.createTypeParameterDeclaration(
+ undefined,
+ ts.factory.createIdentifier('T'),
+ ts.factory.createTypeReferenceNode(
+ ts.factory.createIdentifier('string'),
+ undefined,
+ ),
+ ),
+ ],
+ undefined,
+ [
+ ts.factory.createPropertySignature(
+ undefined,
+ ts.factory.createComputedPropertyName(
+ ts.factory.createIdentifier('kParameters'),
+ ),
undefined,
- ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature(
+ ts.factory.createTypeReferenceNode(
+ ts.factory.createIdentifier('T'),
undefined,
- [ts.factory.createParameterDeclaration(
+ ),
+ ),
+ ],
+ ),
+ ts.factory.createInterfaceDeclaration(
+ [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
+ ts.factory.createIdentifier('ILocale'),
+ undefined,
+ undefined,
+ [
+ ts.factory.createIndexSignature(
+ undefined,
+ [
+ ts.factory.createParameterDeclaration(
undefined,
undefined,
- ts.factory.createIdentifier('lang'),
+ ts.factory.createIdentifier('_'),
undefined,
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
undefined,
- )],
+ ),
+ ],
+ ts.factory.createUnionTypeNode([
+ ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier('Locale'),
+ ts.factory.createIdentifier('ParameterizedString'),
+ [ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)],
+ ),
+ ts.factory.createTypeReferenceNode(
+ ts.factory.createIdentifier('ILocale'),
undefined,
),
- )]),
- undefined,
- )],
- ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags,
+ ]),
+ ),
+ ],
+ ),
+ ts.factory.createInterfaceDeclaration(
+ [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
+ ts.factory.createIdentifier('Locale'),
+ undefined,
+ [
+ ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
+ ts.factory.createExpressionWithTypeArguments(
+ ts.factory.createIdentifier('ILocale'),
+ undefined,
+ ),
+ ]),
+ ],
+ members,
+ ),
+ ts.factory.createVariableStatement(
+ [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
+ ts.factory.createVariableDeclarationList(
+ [
+ ts.factory.createVariableDeclaration(
+ ts.factory.createIdentifier('locales'),
+ undefined,
+ ts.factory.createTypeLiteralNode([
+ ts.factory.createIndexSignature(
+ undefined,
+ [
+ ts.factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ ts.factory.createIdentifier('lang'),
+ undefined,
+ ts.factory.createKeywordTypeNode(
+ ts.SyntaxKind.StringKeyword,
+ ),
+ undefined,
+ ),
+ ],
+ ts.factory.createTypeReferenceNode(
+ ts.factory.createIdentifier('Locale'),
+ undefined,
+ ),
+ ),
+ ]),
+ undefined,
+ ),
+ ],
+ ts.NodeFlags.Const,
),
),
ts.factory.createFunctionDeclaration(
@@ -70,16 +187,28 @@ export default function generateDTS() {
),
ts.factory.createExportDefault(ts.factory.createIdentifier('locales')),
];
- const printed = ts.createPrinter({
- newLine: ts.NewLineKind.LineFeed,
- }).printList(
- ts.ListFormat.MultiLine,
- ts.factory.createNodeArray(elements),
- ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS),
- );
+ const printed = ts
+ .createPrinter({
+ newLine: ts.NewLineKind.LineFeed,
+ })
+ .printList(
+ ts.ListFormat.MultiLine,
+ ts.factory.createNodeArray(elements),
+ ts.createSourceFile(
+ 'index.d.ts',
+ '',
+ ts.ScriptTarget.ESNext,
+ true,
+ ts.ScriptKind.TS,
+ ),
+ );
- fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */
+ fs.writeFileSync(
+ `${__dirname}/index.d.ts`,
+ `/* eslint-disable */
// This file is generated by locales/generateDTS.js
// Do not edit this file directly.
-${printed}`, 'utf-8');
+${printed}`,
+ 'utf-8',
+ );
}
diff --git a/locales/index.d.ts b/locales/index.d.ts
index a659e790cc..a22cb63507 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1,12 +1,19 @@
/* eslint-disable */
// This file is generated by locales/generateDTS.js
// Do not edit this file directly.
-export interface Locale {
+declare const kParameters: unique symbol;
+export interface ParameterizedString {
+ [kParameters]: T;
+}
+export interface ILocale {
+ [_: string]: string | ParameterizedString | ILocale;
+}
+export interface Locale extends ILocale {
"_lang_": string;
"headlineMisskey": string;
"introMisskey": string;
- "poweredByMisskeyDescription": string;
- "monthAndDay": string;
+ "poweredByMisskeyDescription": ParameterizedString<"name">;
+ "monthAndDay": ParameterizedString<"month" | "day">;
"search": string;
"notifications": string;
"username": string;
@@ -18,7 +25,7 @@ export interface Locale {
"cancel": string;
"noThankYou": string;
"enterUsername": string;
- "renotedBy": string;
+ "renotedBy": ParameterizedString<"user">;
"noNotes": string;
"noNotifications": string;
"instance": string;
@@ -78,8 +85,8 @@ export interface Locale {
"export": string;
"files": string;
"download": string;
- "driveFileDeleteConfirm": string;
- "unfollowConfirm": string;
+ "driveFileDeleteConfirm": ParameterizedString<"name">;
+ "unfollowConfirm": ParameterizedString<"name">;
"exportRequested": string;
"importRequested": string;
"lists": string;
@@ -183,9 +190,9 @@ export interface Locale {
"wallpaper": string;
"setWallpaper": string;
"removeWallpaper": string;
- "searchWith": string;
+ "searchWith": ParameterizedString<"q">;
"youHaveNoLists": string;
- "followConfirm": string;
+ "followConfirm": ParameterizedString<"name">;
"proxyAccount": string;
"proxyAccountDescription": string;
"host": string;
@@ -208,7 +215,7 @@ export interface Locale {
"software": string;
"version": string;
"metadata": string;
- "withNFiles": string;
+ "withNFiles": ParameterizedString<"n">;
"monitor": string;
"jobQueue": string;
"cpuAndMemory": string;
@@ -237,7 +244,7 @@ export interface Locale {
"processing": string;
"preview": string;
"default": string;
- "defaultValueIs": string;
+ "defaultValueIs": ParameterizedString<"value">;
"noCustomEmojis": string;
"noJobs": string;
"federating": string;
@@ -266,8 +273,8 @@ export interface Locale {
"imageUrl": string;
"remove": string;
"removed": string;
- "removeAreYouSure": string;
- "deleteAreYouSure": string;
+ "removeAreYouSure": ParameterizedString<"x">;
+ "deleteAreYouSure": ParameterizedString<"x">;
"resetAreYouSure": string;
"areYouSure": string;
"saved": string;
@@ -285,8 +292,8 @@ export interface Locale {
"messageRead": string;
"noMoreHistory": string;
"startMessaging": string;
- "nUsersRead": string;
- "agreeTo": string;
+ "nUsersRead": ParameterizedString<"n">;
+ "agreeTo": ParameterizedString<"0">;
"agree": string;
"agreeBelow": string;
"basicNotesBeforeCreateAccount": string;
@@ -298,7 +305,7 @@ export interface Locale {
"images": string;
"image": string;
"birthday": string;
- "yearsOld": string;
+ "yearsOld": ParameterizedString<"age">;
"registeredDate": string;
"location": string;
"theme": string;
@@ -353,9 +360,9 @@ export interface Locale {
"thisYear": string;
"thisMonth": string;
"today": string;
- "dayX": string;
- "monthX": string;
- "yearX": string;
+ "dayX": ParameterizedString<"day">;
+ "monthX": ParameterizedString<"month">;
+ "yearX": ParameterizedString<"year">;
"pages": string;
"integration": string;
"connectService": string;
@@ -420,7 +427,7 @@ export interface Locale {
"recentlyUpdatedUsers": string;
"recentlyRegisteredUsers": string;
"recentlyDiscoveredUsers": string;
- "exploreUsersCount": string;
+ "exploreUsersCount": ParameterizedString<"count">;
"exploreFediverse": string;
"popularTags": string;
"userList": string;
@@ -437,16 +444,16 @@ export interface Locale {
"moderationNote": string;
"addModerationNote": string;
"moderationLogs": string;
- "nUsersMentioned": string;
+ "nUsersMentioned": ParameterizedString<"n">;
"securityKeyAndPasskey": string;
"securityKey": string;
"lastUsed": string;
- "lastUsedAt": string;
+ "lastUsedAt": ParameterizedString<"t">;
"unregister": string;
"passwordLessLogin": string;
"passwordLessLoginDescription": string;
"resetPassword": string;
- "newPasswordIs": string;
+ "newPasswordIs": ParameterizedString<"password">;
"reduceUiAnimation": string;
"share": string;
"notFound": string;
@@ -466,7 +473,7 @@ export interface Locale {
"enable": string;
"next": string;
"retype": string;
- "noteOf": string;
+ "noteOf": ParameterizedString<"user">;
"quoteAttached": string;
"quoteQuestion": string;
"noMessagesYet": string;
@@ -486,12 +493,12 @@ export interface Locale {
"strongPassword": string;
"passwordMatched": string;
"passwordNotMatched": string;
- "signinWith": string;
+ "signinWith": ParameterizedString<"x">;
"signinFailed": string;
"or": string;
"language": string;
"uiLanguage": string;
- "aboutX": string;
+ "aboutX": ParameterizedString<"x">;
"emojiStyle": string;
"native": string;
"disableDrawer": string;
@@ -509,7 +516,7 @@ export interface Locale {
"regenerate": string;
"fontSize": string;
"mediaListWithOneImageAppearance": string;
- "limitTo": string;
+ "limitTo": ParameterizedString<"x">;
"noFollowRequests": string;
"openImageInNewTab": string;
"dashboard": string;
@@ -587,7 +594,7 @@ export interface Locale {
"deleteAllFiles": string;
"deleteAllFilesConfirm": string;
"removeAllFollowing": string;
- "removeAllFollowingDescription": string;
+ "removeAllFollowingDescription": ParameterizedString<"host">;
"userSuspended": string;
"userSilenced": string;
"yourAccountSuspendedTitle": string;
@@ -658,9 +665,9 @@ export interface Locale {
"wordMute": string;
"hardWordMute": string;
"regexpError": string;
- "regexpErrorDescription": string;
+ "regexpErrorDescription": ParameterizedString<"tab" | "line">;
"instanceMute": string;
- "userSaysSomething": string;
+ "userSaysSomething": ParameterizedString<"name">;
"makeActive": string;
"display": string;
"copy": string;
@@ -686,7 +693,7 @@ export interface Locale {
"abuseReports": string;
"reportAbuse": string;
"reportAbuseRenote": string;
- "reportAbuseOf": string;
+ "reportAbuseOf": ParameterizedString<"name">;
"fillAbuseReportDescription": string;
"abuseReported": string;
"reporter": string;
@@ -701,7 +708,7 @@ export interface Locale {
"defaultNavigationBehaviour": string;
"editTheseSettingsMayBreakAccount": string;
"instanceTicker": string;
- "waitingFor": string;
+ "waitingFor": ParameterizedString<"x">;
"random": string;
"system": string;
"switchUi": string;
@@ -711,10 +718,10 @@ export interface Locale {
"optional": string;
"createNewClip": string;
"unclip": string;
- "confirmToUnclipAlreadyClippedNote": string;
+ "confirmToUnclipAlreadyClippedNote": ParameterizedString<"name">;
"public": string;
"private": string;
- "i18nInfo": string;
+ "i18nInfo": ParameterizedString<"link">;
"manageAccessTokens": string;
"accountInfo": string;
"notesCount": string;
@@ -764,9 +771,9 @@ export interface Locale {
"needReloadToApply": string;
"showTitlebar": string;
"clearCache": string;
- "onlineUsersCount": string;
- "nUsers": string;
- "nNotes": string;
+ "onlineUsersCount": ParameterizedString<"n">;
+ "nUsers": ParameterizedString<"n">;
+ "nNotes": ParameterizedString<"n">;
"sendErrorReports": string;
"sendErrorReportsDescription": string;
"myTheme": string;
@@ -798,7 +805,7 @@ export interface Locale {
"publish": string;
"inChannelSearch": string;
"useReactionPickerForContextMenu": string;
- "typingUsers": string;
+ "typingUsers": ParameterizedString<"users">;
"jumpToSpecifiedDate": string;
"showingPastTimeline": string;
"clear": string;
@@ -865,7 +872,7 @@ export interface Locale {
"misskeyUpdated": string;
"whatIsNew": string;
"translate": string;
- "translatedFrom": string;
+ "translatedFrom": ParameterizedString<"x">;
"accountDeletionInProgress": string;
"usernameInfo": string;
"aiChanMode": string;
@@ -896,11 +903,11 @@ export interface Locale {
"continueThread": string;
"deleteAccountConfirm": string;
"incorrectPassword": string;
- "voteConfirm": string;
+ "voteConfirm": ParameterizedString<"choice">;
"hide": string;
"useDrawerReactionPickerForMobile": string;
- "welcomeBackWithName": string;
- "clickToFinishEmailVerification": string;
+ "welcomeBackWithName": ParameterizedString<"name">;
+ "clickToFinishEmailVerification": ParameterizedString<"ok">;
"overridedDeviceKind": string;
"smartphone": string;
"tablet": string;
@@ -928,8 +935,8 @@ export interface Locale {
"cropYes": string;
"cropNo": string;
"file": string;
- "recentNHours": string;
- "recentNDays": string;
+ "recentNHours": ParameterizedString<"n">;
+ "recentNDays": ParameterizedString<"n">;
"noEmailServerWarning": string;
"thereIsUnresolvedAbuseReportWarning": string;
"recommended": string;
@@ -938,7 +945,7 @@ export interface Locale {
"driveCapOverrideCaption": string;
"requireAdminForView": string;
"isSystemAccount": string;
- "typeToConfirm": string;
+ "typeToConfirm": ParameterizedString<"x">;
"deleteAccount": string;
"document": string;
"numberOfPageCache": string;
@@ -992,7 +999,7 @@ export interface Locale {
"neverShow": string;
"remindMeLater": string;
"didYouLikeMisskey": string;
- "pleaseDonate": string;
+ "pleaseDonate": ParameterizedString<"host">;
"roles": string;
"role": string;
"noRole": string;
@@ -1090,7 +1097,7 @@ export interface Locale {
"preservedUsernamesDescription": string;
"createNoteFromTheFile": string;
"archive": string;
- "channelArchiveConfirmTitle": string;
+ "channelArchiveConfirmTitle": ParameterizedString<"name">;
"channelArchiveConfirmDescription": string;
"thisChannelArchived": string;
"displayOfNote": string;
@@ -1120,8 +1127,8 @@ export interface Locale {
"createCount": string;
"inviteCodeCreated": string;
"inviteLimitExceeded": string;
- "createLimitRemaining": string;
- "inviteLimitResetCycle": string;
+ "createLimitRemaining": ParameterizedString<"limit">;
+ "inviteLimitResetCycle": ParameterizedString<"time" | "limit">;
"expirationDate": string;
"noExpirationDate": string;
"inviteCodeUsedAt": string;
@@ -1134,7 +1141,7 @@ export interface Locale {
"expired": string;
"doYouAgree": string;
"beSureToReadThisAsItIsImportant": string;
- "iHaveReadXCarefullyAndAgree": string;
+ "iHaveReadXCarefullyAndAgree": ParameterizedString<"x">;
"dialog": string;
"icon": string;
"forYou": string;
@@ -1189,7 +1196,7 @@ export interface Locale {
"doReaction": string;
"code": string;
"reloadRequiredToApplySettings": string;
- "remainingN": string;
+ "remainingN": ParameterizedString<"n">;
"overwriteContentConfirm": string;
"seasonalScreenEffect": string;
"decorate": string;
@@ -1202,7 +1209,7 @@ export interface Locale {
"replay": string;
"replaying": string;
"ranking": string;
- "lastNDays": string;
+ "lastNDays": ParameterizedString<"n">;
"backToTitle": string;
"enableHorizontalSwipe": string;
"_bubbleGame": {
@@ -1221,7 +1228,7 @@ export interface Locale {
"end": string;
"tooManyActiveAnnouncementDescription": string;
"readConfirmTitle": string;
- "readConfirmText": string;
+ "readConfirmText": ParameterizedString<"title">;
"shouldNotBeUsedToPresentPermanentInfo": string;
"dialogAnnouncementUxWarn": string;
"silence": string;
@@ -1236,10 +1243,10 @@ export interface Locale {
"theseSettingsCanEditLater": string;
"youCanEditMoreSettingsInSettingsPageLater": string;
"followUsers": string;
- "pushNotificationDescription": string;
+ "pushNotificationDescription": ParameterizedString<"name">;
"initialAccountSettingCompleted": string;
- "haveFun": string;
- "youCanContinueTutorial": string;
+ "haveFun": ParameterizedString<"name">;
+ "youCanContinueTutorial": ParameterizedString<"name">;
"startTutorial": string;
"skipAreYouSure": string;
"laterAreYouSure": string;
@@ -1277,7 +1284,7 @@ export interface Locale {
"social": string;
"global": string;
"description2": string;
- "description3": string;
+ "description3": ParameterizedString<"link">;
};
"_postNote": {
"title": string;
@@ -1315,7 +1322,7 @@ export interface Locale {
};
"_done": {
"title": string;
- "description": string;
+ "description": ParameterizedString<"link">;
};
};
"_timelineDescription": {
@@ -1329,10 +1336,10 @@ export interface Locale {
};
"_serverSettings": {
"iconUrl": string;
- "appIconDescription": string;
+ "appIconDescription": ParameterizedString<"host">;
"appIconUsageExample": string;
"appIconStyleRecommendation": string;
- "appIconResolutionMustBe": string;
+ "appIconResolutionMustBe": ParameterizedString<"resolution">;
"manifestJsonOverride": string;
"shortName": string;
"shortNameDescription": string;
@@ -1343,7 +1350,7 @@ export interface Locale {
"_accountMigration": {
"moveFrom": string;
"moveFromSub": string;
- "moveFromLabel": string;
+ "moveFromLabel": ParameterizedString<"n">;
"moveFromDescription": string;
"moveTo": string;
"moveToLabel": string;
@@ -1351,7 +1358,7 @@ export interface Locale {
"moveAccountDescription": string;
"moveAccountHowTo": string;
"startMigration": string;
- "migrationConfirm": string;
+ "migrationConfirm": ParameterizedString<"account">;
"movedAndCannotBeUndone": string;
"postMigrationNote": string;
"movedTo": string;
@@ -1793,7 +1800,7 @@ export interface Locale {
"_signup": {
"almostThere": string;
"emailAddressInfo": string;
- "emailSent": string;
+ "emailSent": ParameterizedString<"email">;
};
"_accountDelete": {
"accountDelete": string;
@@ -1846,14 +1853,14 @@ export interface Locale {
"save": string;
"inputName": string;
"cannotSave": string;
- "nameAlreadyExists": string;
- "applyConfirm": string;
- "saveConfirm": string;
- "deleteConfirm": string;
- "renameConfirm": string;
+ "nameAlreadyExists": ParameterizedString<"name">;
+ "applyConfirm": ParameterizedString<"name">;
+ "saveConfirm": ParameterizedString<"name">;
+ "deleteConfirm": ParameterizedString<"name">;
+ "renameConfirm": ParameterizedString<"old" | "new">;
"noBackups": string;
- "createdAt": string;
- "updatedAt": string;
+ "createdAt": ParameterizedString<"date" | "time">;
+ "updatedAt": ParameterizedString<"date" | "time">;
"cannotLoad": string;
"invalidFile": string;
};
@@ -1898,8 +1905,8 @@ export interface Locale {
"featured": string;
"owned": string;
"following": string;
- "usersCount": string;
- "notesCount": string;
+ "usersCount": ParameterizedString<"n">;
+ "notesCount": ParameterizedString<"n">;
"nameAndDescription": string;
"nameOnly": string;
"allowRenoteToExternal": string;
@@ -1927,7 +1934,7 @@ export interface Locale {
"manage": string;
"code": string;
"description": string;
- "installed": string;
+ "installed": ParameterizedString<"name">;
"installedThemes": string;
"builtinThemes": string;
"alreadyInstalled": string;
@@ -1950,7 +1957,7 @@ export interface Locale {
"lighten": string;
"inputConstantName": string;
"importInfo": string;
- "deleteConstantConfirm": string;
+ "deleteConstantConfirm": ParameterizedString<"const">;
"keys": {
"accent": string;
"bg": string;
@@ -2013,23 +2020,23 @@ export interface Locale {
"_ago": {
"future": string;
"justNow": string;
- "secondsAgo": string;
- "minutesAgo": string;
- "hoursAgo": string;
- "daysAgo": string;
- "weeksAgo": string;
- "monthsAgo": string;
- "yearsAgo": string;
+ "secondsAgo": ParameterizedString<"n">;
+ "minutesAgo": ParameterizedString<"n">;
+ "hoursAgo": ParameterizedString<"n">;
+ "daysAgo": ParameterizedString<"n">;
+ "weeksAgo": ParameterizedString<"n">;
+ "monthsAgo": ParameterizedString<"n">;
+ "yearsAgo": ParameterizedString<"n">;
"invalid": string;
};
"_timeIn": {
- "seconds": string;
- "minutes": string;
- "hours": string;
- "days": string;
- "weeks": string;
- "months": string;
- "years": string;
+ "seconds": ParameterizedString<"n">;
+ "minutes": ParameterizedString<"n">;
+ "hours": ParameterizedString<"n">;
+ "days": ParameterizedString<"n">;
+ "weeks": ParameterizedString<"n">;
+ "months": ParameterizedString<"n">;
+ "years": ParameterizedString<"n">;
};
"_time": {
"second": string;
@@ -2040,7 +2047,7 @@ export interface Locale {
"_2fa": {
"alreadyRegistered": string;
"registerTOTP": string;
- "step1": string;
+ "step1": ParameterizedString<"a" | "b">;
"step2": string;
"step2Click": string;
"step2Uri": string;
@@ -2055,7 +2062,7 @@ export interface Locale {
"securityKeyName": string;
"tapSecurityKey": string;
"removeKey": string;
- "removeKeyConfirm": string;
+ "removeKeyConfirm": ParameterizedString<"name">;
"whyTOTPOnlyRenew": string;
"renewTOTP": string;
"renewTOTPConfirm": string;
@@ -2156,9 +2163,9 @@ export interface Locale {
};
"_auth": {
"shareAccessTitle": string;
- "shareAccess": string;
+ "shareAccess": ParameterizedString<"name">;
"shareAccessAsk": string;
- "permission": string;
+ "permission": ParameterizedString<"name">;
"permissionAsk": string;
"pleaseGoBack": string;
"callback": string;
@@ -2217,12 +2224,12 @@ export interface Locale {
"_cw": {
"hide": string;
"show": string;
- "chars": string;
- "files": string;
+ "chars": ParameterizedString<"count">;
+ "files": ParameterizedString<"count">;
};
"_poll": {
"noOnlyOneChoice": string;
- "choiceN": string;
+ "choiceN": ParameterizedString<"n">;
"noMore": string;
"canMultipleVote": string;
"expiration": string;
@@ -2232,16 +2239,16 @@ export interface Locale {
"deadlineDate": string;
"deadlineTime": string;
"duration": string;
- "votesCount": string;
- "totalVotes": string;
+ "votesCount": ParameterizedString<"n">;
+ "totalVotes": ParameterizedString<"n">;
"vote": string;
"showResult": string;
"voted": string;
"closed": string;
- "remainingDays": string;
- "remainingHours": string;
- "remainingMinutes": string;
- "remainingSeconds": string;
+ "remainingDays": ParameterizedString<"d" | "h">;
+ "remainingHours": ParameterizedString<"h" | "m">;
+ "remainingMinutes": ParameterizedString<"m" | "s">;
+ "remainingSeconds": ParameterizedString<"s">;
};
"_visibility": {
"public": string;
@@ -2281,7 +2288,7 @@ export interface Locale {
"changeAvatar": string;
"changeBanner": string;
"verifiedLinkDescription": string;
- "avatarDecorationMax": string;
+ "avatarDecorationMax": ParameterizedString<"max">;
};
"_exportOrImport": {
"allNotes": string;
@@ -2404,16 +2411,16 @@ export interface Locale {
};
"_notification": {
"fileUploaded": string;
- "youGotMention": string;
- "youGotReply": string;
- "youGotQuote": string;
- "youRenoted": string;
+ "youGotMention": ParameterizedString<"name">;
+ "youGotReply": ParameterizedString<"name">;
+ "youGotQuote": ParameterizedString<"name">;
+ "youRenoted": ParameterizedString<"name">;
"youWereFollowed": string;
"youReceivedFollowRequest": string;
"yourFollowRequestAccepted": string;
"pollEnded": string;
"newNote": string;
- "unreadAntennaNote": string;
+ "unreadAntennaNote": ParameterizedString<"name">;
"roleAssigned": string;
"emptyPushNotificationMessage": string;
"achievementEarned": string;
@@ -2421,9 +2428,9 @@ export interface Locale {
"checkNotificationBehavior": string;
"sendTestNotification": string;
"notificationWillBeDisplayedLikeThis": string;
- "reactedBySomeUsers": string;
- "renotedBySomeUsers": string;
- "followedBySomeUsers": string;
+ "reactedBySomeUsers": ParameterizedString<"n">;
+ "renotedBySomeUsers": ParameterizedString<"n">;
+ "followedBySomeUsers": ParameterizedString<"n">;
"_types": {
"all": string;
"note": string;
@@ -2480,8 +2487,8 @@ export interface Locale {
};
};
"_dialog": {
- "charactersExceeded": string;
- "charactersBelow": string;
+ "charactersExceeded": ParameterizedString<"current" | "max">;
+ "charactersBelow": ParameterizedString<"current" | "min">;
};
"_disabledTimeline": {
"title": string;
diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts
index 858db74dac..c5c4ccf820 100644
--- a/packages/frontend/src/i18n.ts
+++ b/packages/frontend/src/i18n.ts
@@ -10,6 +10,7 @@ import { I18n } from '@/scripts/i18n.js';
export const i18n = markRaw(new I18n(locale));
-export function updateI18n(newLocale) {
- i18n.ts = newLocale;
+export function updateI18n(newLocale: Locale) {
+ // @ts-expect-error -- private field
+ i18n.locale = newLocale;
}
diff --git a/packages/frontend/src/scripts/i18n.ts b/packages/frontend/src/scripts/i18n.ts
index 8e5f17f38a..55b5371950 100644
--- a/packages/frontend/src/scripts/i18n.ts
+++ b/packages/frontend/src/scripts/i18n.ts
@@ -2,33 +2,114 @@
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import type { ILocale, ParameterizedString } from '../../../../locales/index.js';
-export class I18n> {
- public ts: T;
+type FlattenKeys = keyof {
+ [K in keyof T as T[K] extends ILocale
+ ? FlattenKeys extends infer C extends string
+ ? `${K & string}.${C}`
+ : never
+ : T[K] extends TPrediction
+ ? K
+ : never]: T[K];
+};
- constructor(locale: T) {
- this.ts = locale;
+type ParametersOf>> = T extends ILocale
+ ? TKey extends `${infer K}.${infer C}`
+ // @ts-expect-error -- C は明らかに FlattenKeys> になるが、型システムはここでは TKey がドット区切りであることのコンテキストを持たないので、型システムに合法にて示すことはできない。
+ ? ParametersOf
+ : TKey extends keyof T
+ ? T[TKey] extends ParameterizedString
+ ? P
+ : never
+ : never
+ : never;
+type Ts = {
+ readonly [K in keyof T as T[K] extends ParameterizedString ? never : K]: T[K] extends ILocale ? Ts : string;
+};
+
+export class I18n {
+ constructor(private locale: T) {
//#region BIND
this.t = this.t.bind(this);
//#endregion
}
- // string にしているのは、ドット区切りでのパス指定を許可するため
- // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
- public t(key: string, args?: Record): string {
- try {
- let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string;
+ public get ts(): Ts {
+ if (_DEV_) {
+ class Handler implements ProxyHandler {
+ get(target: TTarget, p: string | symbol): unknown {
+ const value = target[p as keyof TTarget];
+
+ if (typeof value === 'object') {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- 実際には null がくることはないので。
+ return new Proxy(value!, new Handler());
+ }
+
+ if (typeof value === 'string') {
+ const parameters = Array.from(value.matchAll(/\{(\w+)\}/g)).map(([, parameter]) => parameter);
+
+ if (parameters.length) {
+ console.error(`Missing locale parameters: ${parameters.join(', ')} at ${String(p)}`);
+ }
+
+ return value;
+ }
+
+ console.error(`Unexpected locale key: ${String(p)}`);
+
+ return p;
+ }
+ }
+
+ return new Proxy(this.locale, new Handler()) as Ts;
+ }
+
+ return this.locale as Ts;
+ }
+
+ /**
+ * @deprecated なるべくこのメソッド使うよりも locale 直接参照の方が vue のキャッシュ効いてパフォーマンスが良いかも
+ */
+ public t>(key: TKey): string;
+ public t>>(key: TKey, args: { readonly [_ in ParametersOf]: string | number }): string;
+ public t(key: string, args?: { readonly [_: string]: string | number }) {
+ let str: string | ParameterizedString | ILocale = this.locale;
+
+ for (const k of key.split('.')) {
+ str = str[k];
- if (args) {
- for (const [k, v] of Object.entries(args)) {
- str = str.replace(`{${k}}`, v.toString());
+ if (_DEV_) {
+ if (typeof str === 'undefined') {
+ console.error(`Unexpected locale key: ${key}`);
+ return key;
}
}
- return str;
- } catch (err) {
- console.warn(`missing localization '${key}'`);
- return key;
}
+
+ if (args) {
+ if (_DEV_) {
+ const missing = Array.from((str as string).matchAll(/\{(\w+)\}/g), ([, parameter]) => parameter).filter(parameter => !Object.hasOwn(args, parameter));
+
+ if (missing.length) {
+ console.error(`Missing locale parameters: ${missing.join(', ')} at ${key}`);
+ }
+ }
+
+ for (const [k, v] of Object.entries(args)) {
+ const search = `{${k}}`;
+
+ if (_DEV_) {
+ if (!(str as string).includes(search)) {
+ console.error(`Unexpected locale parameter: ${k} at ${key}`);
+ }
+ }
+
+ str = (str as string).replace(search, v.toString());
+ }
+ }
+
+ return str;
}
}
--
cgit v1.2.3-freya
From d85085d16fb6952e742eeca38b98f9442b7ec984 Mon Sep 17 00:00:00 2001
From: "Acid Chicken (硫酸鶏)"
Date: Fri, 19 Jan 2024 11:54:00 +0900
Subject: refactor: style
---
packages/frontend/src/scripts/i18n.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'packages/frontend/src/scripts')
diff --git a/packages/frontend/src/scripts/i18n.ts b/packages/frontend/src/scripts/i18n.ts
index 55b5371950..3366f3eac3 100644
--- a/packages/frontend/src/scripts/i18n.ts
+++ b/packages/frontend/src/scripts/i18n.ts
@@ -48,7 +48,7 @@ export class I18n {
}
if (typeof value === 'string') {
- const parameters = Array.from(value.matchAll(/\{(\w+)\}/g)).map(([, parameter]) => parameter);
+ const parameters = Array.from(value.matchAll(/\{(\w+)\}/g), ([, parameter]) => parameter);
if (parameters.length) {
console.error(`Missing locale parameters: ${parameters.join(', ')} at ${String(p)}`);
--
cgit v1.2.3-freya
From 678dba92451ef9b94bc40ea7de5ddd0e24249829 Mon Sep 17 00:00:00 2001
From: 1Step621 <86859447+1STEP621@users.noreply.github.com>
Date: Fri, 19 Jan 2024 18:50:26 +0900
Subject: Enhance(frontend): MFMの属性にオートコンプリートが利用できるように
(#12803)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* MFMのパラメータでオートコンプリートできるように
* tweak conditions & refactor
* ファイル末尾の改行忘れ
* remove console.log & refactor
* 型付けに敗北
* fix
* update CHANGELOG.md
* tweak conditions
* CHANGELOGの様式ミス
* CHANGELOGを書く場所を間違えていたので修正
* move changelog
* move changelog
* typeof MFM_TAGS[number]
Co-authored-by: syuilo
* $[border.noclip ]対応
* Update const.ts
---------
Co-authored-by: syuilo
---
CHANGELOG.md | 3 +-
.../frontend/src/components/MkAutocomplete.vue | 17 ++++++++--
packages/frontend/src/const.ts | 24 +++++++++++++
packages/frontend/src/scripts/autocomplete.ts | 39 +++++++++++++++++++---
4 files changed, 75 insertions(+), 8 deletions(-)
(limited to 'packages/frontend/src/scripts')
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bb761bd96..e6dd704c5b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,5 @@
- (:{{ customEmojiTree.length }} :{{ emojis.length }})
+ (:{{ customEmojiTree?.length }} :{{ emojis.length }})
import { ref, computed, Ref } from 'vue';
import { CustomEmojiFolderTree, getEmojiName } from '@/scripts/emojilist.js';
-import { i18n } from '../i18n.js';
+import { i18n } from '@/i18n.js';
import { customEmojis } from '@/custom-emojis.js';
import MkEmojiPickerSection from '@/components/MkEmojiPicker.section.vue';
@@ -87,7 +87,7 @@ function computeButtonTitle(ev: MouseEvent): void {
elm.title = getEmojiName(emoji) ?? emoji;
}
-function nestedChosen(emoji: any, ev?: MouseEvent) {
+function nestedChosen(emoji: any, ev: MouseEvent) {
emit('chosen', emoji, ev);
}
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 84424c58ed..58160cdf5b 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+