From 558b271cf54877a4ee12241a27bc2cbb406ff928 Mon Sep 17 00:00:00 2001
From: dangered wolf <d@ngeredwolf.me>
Date: Tue, 30 Apr 2024 01:27:53 -0400
Subject: [PATCH] Use intl for formats

---
 src/embed/status.ts       |  3 ++-
 src/render/instantview.ts | 49 +++++++++++++++++++++------------------
 src/types/types.d.ts      |  1 +
 3 files changed, 30 insertions(+), 23 deletions(-)

diff --git a/src/embed/status.ts b/src/embed/status.ts
index 3a6b921..e8bcc55 100644
--- a/src/embed/status.ts
+++ b/src/embed/status.ts
@@ -209,7 +209,8 @@ export const handleStatus = async (
         status: status,
         thread: thread,
         text: newText,
-        flags: flags
+        flags: flags,
+        targetLanguage: language ?? status.lang ?? 'en'
       });
       headers.push(...instructions.addHeaders);
       if (instructions.authorText) {
diff --git a/src/render/instantview.ts b/src/render/instantview.ts
index 534ee09..17cad9f 100644
--- a/src/render/instantview.ts
+++ b/src/render/instantview.ts
@@ -56,11 +56,16 @@ const generateStatusMedia = (status: APIStatus): string => {
 //   return `${hh}:${min} - ${yyyy}/${mm}/${dd}`;
 // }
 
-const formatDate = (date: Date): string => {
-  const yyyy = date.getFullYear();
-  const mm = String(date.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed
-  const dd = String(date.getDate()).padStart(2, '0');
-  return `${yyyy}/${mm}/${dd}`;
+const formatDate = (date: Date, language: string): string => {
+  if (language.startsWith('en')) {
+    language = 'en-CA'; // Use ISO dates for English to avoid problems with mm/dd vs. dd/mm
+  }
+  const formatter = new Intl.DateTimeFormat(language, {
+    year: 'numeric',
+    month: '2-digit',
+    day: '2-digit'
+  });
+  return formatter.format(date);
 };
 
 const htmlifyLinks = (input: string): string => {
@@ -108,15 +113,14 @@ function getTranslatedText(status: APITwitterStatus, isQuote = false): string |
 
 const notApplicableComment = '<!-- N/A -->';
 
-// 1100 -> 1.1K, 1100000 -> 1.1M
-const truncateSocialCount = (count: number): string => {
-  if (count >= 1000000) {
-    return `${(count / 1000000).toFixed(1)}M`;
-  } else if (count >= 1000) {
-    return `${(count / 1000).toFixed(1)}K`;
-  } else {
-    return String(count);
-  }
+const truncateSocialCount = (count: number, locale = 'en-US') => {
+  const formatter = new Intl.NumberFormat(locale, {
+    notation: "compact",
+    compactDisplay: "short",
+    maximumFractionDigits: 1
+  });
+
+  return formatter.format(count);
 };
 
 const generateInlineAuthorHeader = (
@@ -166,7 +170,7 @@ const wrapForeignLinks = (url: string) => {
     : url;
 };
 
-const generateStatusFooter = (status: APIStatus, isQuote = false, author: APIUser): string => {
+const generateStatusFooter = (status: APIStatus, isQuote = false, author: APIUser, language: string): string => {
   let description = author.description;
   description = htmlifyLinks(description);
   description = htmlifyHashtags(description);
@@ -200,10 +204,10 @@ const generateStatusFooter = (status: APIStatus, isQuote = false, author: APIUse
           website: author.website
             ? `🔗 <a rel="nofollow" href="${wrapForeignLinks(author.website.url)}">${author.website.display_url}</a>`
             : '',
-          joined: author.joined ? `📆 ${formatDate(new Date(author.joined))}` : '',
-          following: truncateSocialCount(author.following),
-          followers: truncateSocialCount(author.followers),
-          statuses: truncateSocialCount(author.statuses)
+          joined: author.joined ? `📆 ${formatDate(new Date(author.joined), language)}` : '',
+          following: truncateSocialCount(author.following, language),
+          followers: truncateSocialCount(author.followers, language),
+          statuses: truncateSocialCount(author.statuses, language)
         })
   });
 };
@@ -270,6 +274,7 @@ const generateCommunityNote = (status: APITwitterStatus): string => {
 const generateStatus = (
   status: APIStatus,
   author: APIUser,
+  language: string,
   isQuote = false,
   authorActionType: AuthorActionType | null
 ): string => {
@@ -295,7 +300,7 @@ const generateStatus = (
   <!-- Embed poll -->
   ${status.poll ? generatePoll(status.poll, status.lang ?? 'en') : notApplicableComment}
   <!-- Embedded quote status -->
-  ${!isQuote && status.quote ? generateStatus(status.quote, author, true, null) : notApplicableComment}
+  ${!isQuote && status.quote ? generateStatus(status.quote, author, language, true, null) : notApplicableComment}
   <br>${!isQuote ? `<a href="${status.url}">${i18next.t('ivViewOriginal')}</a>` : notApplicableComment}
   `.format({
     quoteHeader: isQuote
@@ -387,10 +392,10 @@ export const renderInstantView = (properties: RenderProperties): ResponseInstruc
 
         previousThreadPieceAuthor = status.author?.id;
 
-        return generateStatus(status, status.author ?? thread?.author, false, authorAction);
+        return generateStatus(status, status.author ?? thread?.author, properties?.targetLanguage ?? 'en', false, authorAction,);
       })
       .join('')}
-    ${generateStatusFooter(status, false, thread?.author ?? status.author)}
+    ${generateStatusFooter(status, false, thread?.author ?? status.author, properties?.targetLanguage ?? 'en')}
     <br>${`<a href="${status.url}">${i18next.t('ivViewOriginal')}</a>`}
   </article>`;
 
diff --git a/src/types/types.d.ts b/src/types/types.d.ts
index 3b5b947..ae8964e 100644
--- a/src/types/types.d.ts
+++ b/src/types/types.d.ts
@@ -38,6 +38,7 @@ interface RenderProperties {
   userAgent?: string;
   text?: string;
   flags?: InputFlags;
+  targetLanguage?: string;
 }
 
 interface TweetAPIResponse {