From c3a36698e5e57418d791c2a77f7fdda284b76e5d Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sun, 1 Jan 2023 17:11:33 +0900
Subject: [PATCH] use Intl.DateTimeFormat and Intl.NumberFormat instead of
 toLocaleString (#9444)

---
 packages/frontend/src/components/MkAbuseReport.vue   |  3 ++-
 packages/frontend/src/components/MkChart.vue         |  3 ++-
 .../frontend/src/components/MkFileListForAdmin.vue   |  3 ++-
 packages/frontend/src/components/MkUrlPreview.vue    |  7 +++----
 packages/frontend/src/components/MkYoutubePlayer.vue |  6 ++----
 packages/frontend/src/components/global/MkTime.vue   |  3 ++-
 packages/frontend/src/filters/date.ts                |  4 ++++
 packages/frontend/src/filters/number.ts              |  4 +++-
 packages/frontend/src/pages/about.federation.vue     |  3 ++-
 packages/frontend/src/pages/admin/users.vue          |  3 ++-
 packages/frontend/src/pages/instance-info.vue        |  3 ++-
 packages/frontend/src/pages/note.vue                 |  3 ++-
 packages/frontend/src/pages/user/home.vue            |  3 ++-
 packages/frontend/src/scripts/intl-const.ts          | 12 ++++++++++++
 14 files changed, 42 insertions(+), 18 deletions(-)
 create mode 100644 packages/frontend/src/filters/date.ts
 create mode 100644 packages/frontend/src/scripts/intl-const.ts

diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue
index 9a3464b64..c06579288 100644
--- a/packages/frontend/src/components/MkAbuseReport.vue
+++ b/packages/frontend/src/components/MkAbuseReport.vue
@@ -10,7 +10,7 @@
 		</MkA>
 		<MkKeyValue class="_formBlock">
 			<template #key>{{ i18n.ts.registeredDate }}</template>
-			<template #value>{{ new Date(report.targetUser.createdAt).toLocaleString() }} (<MkTime :time="report.targetUser.createdAt"/>)</template>
+			<template #value>{{ dateString(report.targetUser.createdAt) }} (<MkTime :time="report.targetUser.createdAt"/>)</template>
 		</MkKeyValue>
 	</div>
 	<div class="detail">
@@ -42,6 +42,7 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
 import { acct, userPage } from '@/filters/user';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
+import { dateString } from '@/filters/date';
 
 const props = defineProps<{
 	report: any;
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index 94cffa12c..d99a5478e 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -40,6 +40,7 @@ import { defaultStore } from '@/store';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip';
 import { chartVLine } from '@/scripts/chart-vline';
 import { alpha } from '@/scripts/color';
+import date from '@/filters/date';
 
 const props = defineProps({
 	src: {
@@ -171,7 +172,7 @@ const render = () => {
 	chartInstance = new Chart(chartEl.value, {
 		type: props.bar ? 'bar' : 'line',
 		data: {
-			labels: new Array(props.limit).fill(0).map((_, i) => getDate(i).toLocaleString()).slice().reverse(),
+			labels: new Array(props.limit).fill(0).map((_, i) => date(getDate(i))).slice().reverse(),
 			datasets: chartData.series.map((x, i) => ({
 				parsing: false,
 				label: x.name,
diff --git a/packages/frontend/src/components/MkFileListForAdmin.vue b/packages/frontend/src/components/MkFileListForAdmin.vue
index 4910506a9..1335f88a7 100644
--- a/packages/frontend/src/components/MkFileListForAdmin.vue
+++ b/packages/frontend/src/components/MkFileListForAdmin.vue
@@ -4,7 +4,7 @@
 		<MkA
 			v-for="file in items"
 			:key="file.id"
-			v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${new Date(file.createdAt).toLocaleString()}\nby ${file.user ? '@' + Acct.toString(file.user) : 'system'}`"
+			v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${dateString(file.createdAt)}\nby ${file.user ? '@' + Acct.toString(file.user) : 'system'}`"
 			:to="`/admin/file/${file.id}`"
 			class="file _button"
 		>
@@ -39,6 +39,7 @@ import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
 import bytes from '@/filters/bytes';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
+import { dateString } from '@/filters/date';
 
 const props = defineProps<{
 	pagination: any;
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index f8484e38a..6b38080f9 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -45,11 +45,12 @@
 
 <script lang="ts" setup>
 import { defineAsyncComponent, onMounted, onUnmounted } from 'vue';
-import { url as local, lang } from '@/config';
+import { url as local } from '@/config';
 import { i18n } from '@/i18n';
 import * as os from '@/os';
 import { deviceKind } from '@/scripts/device-kind';
 import MkButton from '@/components/MkButton.vue';
+import { versatileLang } from '@/scripts/intl-const';
 
 const props = withDefaults(defineProps<{
 	url: string;
@@ -95,11 +96,9 @@ if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/
 	requestUrl.hostname = 'www.youtube.com';
 }
 
-const requestLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
-
 requestUrl.hash = '';
 
-window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => {
+window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`).then(res => {
 	res.json().then(info => {
 		if (info.url == null) {
 			unknownUrl = true;
diff --git a/packages/frontend/src/components/MkYoutubePlayer.vue b/packages/frontend/src/components/MkYoutubePlayer.vue
index 5c675fe98..c12b03572 100644
--- a/packages/frontend/src/components/MkYoutubePlayer.vue
+++ b/packages/frontend/src/components/MkYoutubePlayer.vue
@@ -19,7 +19,7 @@
 
 <script lang="ts" setup>
 import XWindow from '@/components/MkWindow.vue';
-import { lang } from '@/config';
+import { versatileLang } from '@/scripts/intl-const';
 
 const props = defineProps<{
 	url: string;
@@ -35,11 +35,9 @@ let player = $ref({
 	height: null,
 });
 
-const requestLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
-
 const ytFetch = (): void => {
 	fetching = true;
-	window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => {
+	window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`).then(res => {
 		res.json().then(info => {
 			if (info.url == null) return;
 			title = info.title;
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index f72b153f5..0bbb0f539 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -9,6 +9,7 @@
 <script lang="ts" setup>
 import { onUnmounted } from 'vue';
 import { i18n } from '@/i18n';
+import { dateTimeFormat } from '@/scripts/intl-const';
 
 const props = withDefaults(defineProps<{
 	time: Date | string;
@@ -18,7 +19,7 @@ const props = withDefaults(defineProps<{
 });
 
 const _time = typeof props.time === 'string' ? new Date(props.time) : props.time;
-const absolute = _time.toLocaleString();
+const absolute = dateTimeFormat.format(_time);
 
 let now = $shallowRef(new Date());
 const relative = $computed(() => {
diff --git a/packages/frontend/src/filters/date.ts b/packages/frontend/src/filters/date.ts
new file mode 100644
index 000000000..706b7d60c
--- /dev/null
+++ b/packages/frontend/src/filters/date.ts
@@ -0,0 +1,4 @@
+import { dateTimeFormat } from '@/scripts/intl-const';
+
+export default (d: Date | number | undefined) => dateTimeFormat.format(d); 
+export const dateString = (d: string) => dateTimeFormat.format(new Date(d));
diff --git a/packages/frontend/src/filters/number.ts b/packages/frontend/src/filters/number.ts
index 880a848ca..6361ba531 100644
--- a/packages/frontend/src/filters/number.ts
+++ b/packages/frontend/src/filters/number.ts
@@ -1 +1,3 @@
-export default n => n == null ? 'N/A' : n.toLocaleString();
+import { numberFormat } from '@/scripts/intl-const';
+
+export default n => n == null ? 'N/A' : numberFormat.format(n);
diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index 6c92ab126..2b20f383d 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -38,7 +38,7 @@
 
 	<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
 		<div class="dqokceoi">
-			<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Last communicated: ${new Date(instance.lastCommunicatedAt).toLocaleString()}\nStatus: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
+			<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Last communicated: ${dateString(instance.lastCommunicatedAt)}\nStatus: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
 				<MkInstanceCardMini :instance="instance"/>
 			</MkA>
 		</div>
@@ -56,6 +56,7 @@ import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
 import FormSplit from '@/components/form/split.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
+import { dateString } from '@/filters/date';
 
 let host = $ref('');
 let state = $ref('federating');
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index d466e2190..24ce98a02 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -41,7 +41,7 @@
 					</div>
 
 					<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination" class="users">
-						<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${new Date(user.updatedAt).toLocaleString()}`" class="user" :to="`/user-info/${user.id}`">
+						<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/user-info/${user.id}`">
 							<MkUserCardMini :user="user"/>
 						</MkA>
 					</MkPagination>
@@ -63,6 +63,7 @@ import { lookupUser } from '@/scripts/lookup-user';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import { dateString } from '@/filters/date';
 
 let paginationComponent = $ref<InstanceType<typeof MkPagination>>();
 
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index a2a125436..f750fdcee 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -101,7 +101,7 @@
 		</div>
 		<div v-else-if="tab === 'users'" class="_formRoot">
 			<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;">
-				<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${new Date(user.updatedAt).toLocaleString()}`" class="user" :to="`/user-info/${user.id}`">
+				<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/user-info/${user.id}`">
 					<MkUserCardMini :user="user"/>
 				</MkA>
 			</MkPagination>
@@ -135,6 +135,7 @@ import { i18n } from '@/i18n';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
+import { dateString } from '@/filters/date';
 
 const props = defineProps<{
 	host: string;
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index 90fe417a7..3019b6eb4 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -51,6 +51,7 @@ import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
+import { dateString } from '@/filters/date';
 
 const props = defineProps<{
 	noteId: string;
@@ -127,7 +128,7 @@ const headerTabs = $computed(() => []);
 
 definePageMetadata(computed(() => note ? {
 	title: i18n.ts.note,
-	subtitle: new Date(note.createdAt).toLocaleString(),
+	subtitle: dateString(note.createdAt),
 	avatar: note.user,
 	path: `/notes/${note.id}`,
 	share: {
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 55420ebb2..45334a998 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -55,7 +55,7 @@
 						</dl>
 						<dl class="field">
 							<dt class="name"><i class="ti ti-calendar ti-fw"></i> {{ i18n.ts.registeredDate }}</dt>
-							<dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd>
+							<dd class="value">{{ dateString(user.createdAt) }} (<MkTime :time="user.createdAt"/>)</dd>
 						</dl>
 					</div>
 					<div v-if="user.fields.length > 0" class="fields">
@@ -127,6 +127,7 @@ import * as os from '@/os';
 import { useRouter } from '@/router';
 import { i18n } from '@/i18n';
 import { $i } from '@/account';
+import { dateString } from '@/filters/date';
 
 const XPhotos = defineAsyncComponent(() => import('./index.photos.vue'));
 const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
diff --git a/packages/frontend/src/scripts/intl-const.ts b/packages/frontend/src/scripts/intl-const.ts
new file mode 100644
index 000000000..081ff6248
--- /dev/null
+++ b/packages/frontend/src/scripts/intl-const.ts
@@ -0,0 +1,12 @@
+import { lang } from '@/config';
+
+export const versatileLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
+export const dateTimeFormat = new Intl.DateTimeFormat(versatileLang, {
+    year: 'numeric',
+    month: 'numeric',
+    day: 'numeric',
+    hour: 'numeric',
+    minute: 'numeric',
+    second: 'numeric',
+});
+export const numberFormat = new Intl.NumberFormat(versatileLang);