diff --git a/locales/generateDTS.js b/locales/generateDTS.js
index d3afdd6e15028d09f3289cd1ca07d242aaf820f2..6eb5bd630daf2484ba2fd172ef1493e4a9fff26c 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 a659e790cc49aa9ada8a858d48e206b774cfc493..a22cb6350716c1bebc8c5e79ce3b06fb270da7bc 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<T extends string> {
+    [kParameters]: T;
+}
+export interface ILocale {
+    [_: string]: string | ParameterizedString<string> | 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 858db74dacf990412f673cc1b524e84cb8c880a7..c5c4ccf820283b4553e6893e2cb5286fb40ad354 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>(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 8e5f17f38ae0dbe30f7f82ecd04642e72eef0ba8..55b537195096d2f72ec8a9bc5dc985181935b484 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<T extends Record<string, any>> {
-	public ts: T;
+type FlattenKeys<T extends ILocale, TPrediction> = keyof {
+	[K in keyof T as T[K] extends ILocale
+		? FlattenKeys<T[K], TPrediction> 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 FlattenKeys<T, ParameterizedString<string>>> = T extends ILocale
+	? TKey extends `${infer K}.${infer C}`
+		// @ts-expect-error -- C は明らかに FlattenKeys<T[K], ParameterizedString<string>> になるが、型システムはここでは TKey がドット区切りであることのコンテキストを持たないので、型システムに合法にて示すことはできない。
+		? ParametersOf<T[K], C>
+		: TKey extends keyof T
+			? T[TKey] extends ParameterizedString<infer P>
+				? P
+				: never
+			: never
+	: never;
 
+type Ts<T extends ILocale> = {
+	readonly [K in keyof T as T[K] extends ParameterizedString<string> ? never : K]: T[K] extends ILocale ? Ts<T[K]> : string;
+};
+
+export class I18n<T extends ILocale> {
+	constructor(private locale: T) {
 		//#region BIND
 		this.t = this.t.bind(this);
 		//#endregion
 	}
 
-	// string にしているのは、ドット区切りでのパス指定を許可するため
-	// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
-	public t(key: string, args?: Record<string, string | number>): string {
-		try {
-			let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string;
+	public get ts(): Ts<T> {
+		if (_DEV_) {
+			class Handler<TTarget extends object> implements ProxyHandler<TTarget> {
+				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<TTarget[keyof TTarget] & object>());
+					}
+
+					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<T>;
+		}
+
+		return this.locale as Ts<T>;
+	}
+
+	/**
+	 * @deprecated なるべくこのメソッド使うよりも locale 直接参照の方が vue のキャッシュ効いてパフォーマンスが良いかも
+	 */
+	public t<TKey extends FlattenKeys<T, string>>(key: TKey): string;
+	public t<TKey extends FlattenKeys<T, ParameterizedString<string>>>(key: TKey, args: { readonly [_ in ParametersOf<T, TKey>]: string | number }): string;
+	public t(key: string, args?: { readonly [_: string]: string | number }) {
+		let str: string | ParameterizedString<string> | 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;
 	}
 }