From 11e5a888d69763a20fdc84d01ab56c40d34ab5cc Mon Sep 17 00:00:00 2001 From: dangered wolf Date: Fri, 9 Feb 2024 18:58:56 -0500 Subject: [PATCH] Add i18n for most visible strings --- i18n/resources.json | 59 +++++++++ package-lock.json | 186 +---------------------------- package.json | 2 +- src/embed/status.ts | 32 +++-- src/providers/twitter/processor.ts | 4 +- src/render/instantview.ts | 53 ++++---- src/types/types.d.ts | 4 +- 7 files changed, 117 insertions(+), 223 deletions(-) create mode 100644 i18n/resources.json diff --git a/i18n/resources.json b/i18n/resources.json new file mode 100644 index 0000000..3058865 --- /dev/null +++ b/i18n/resources.json @@ -0,0 +1,59 @@ +{ + "en": { + "translation": { + "translatedFrom": "Translated from {language}", + "quotedFrom": "Quoting {name} (@{screen_name})", + "replyingTo": "Replying to @{screen_name}", + "threadPartHeader": "A part of @${status.author.screen_name}'s thread", + + "videoAltTextUnavailable": "{author}'s video. Alt text not available.", + "gifAltTextUnavailable": "{author}'s GIF. Alt text not available.", + + "ivOriginalText": "Original text", + "ivViewOriginal": "View full thread", + "ivViewOriginalStatus": "View full thread", + "ivAboutAuthor": "About author", + "ivProfileFollowing": "Following", + "ivProfileFollowers": "Followers", + "ivProfileStatuses": "Posts", + + "ivFallbackText": "If you can see this, your browser is doing something weird with your user agent.", + "ivInternetArchiveText": "{brandingName} archive", + + "pollFinalResults": "Final results", + "pollVotes": "{voteCount} votes ยท {timeLeft}", + + "ivQuoteHeader": "Quoting {authorName} (@{authorHandle})", + + "language_af": "Afrikaans", + "language_ar": "Arabic", + "language_ca": "Catalan", + "language_cs": "Czech", + "language_da": "Danish", + "language_de": "German", + "language_en": "English", + "language_el": "Greek", + "language_es": "Spanish", + "language_fi": "Finnish", + "language_fr": "French", + "language_he": "Hebrew", + "language_hu": "Hungarian", + "language_it": "Italian", + "language_ja": "Japanese", + "language_ko": "Korean", + "language_nl": "Dutch", + "language_no": "Norwegian", + "language_pl": "Polish", + "language_pt": "Portuguese", + "language_ro": "Romanian", + "language_ru": "Russian", + "language_sr": "Serbian", + "language_sv": "Swedish", + "language_tr": "Turkish", + "language_uk": "Ukrainian", + "language_vi": "Vietnamese", + "language_zh-CN": "Chinese", + "language_zh-TW": "Chinese" + } + } +} diff --git a/package-lock.json b/package-lock.json index 5f604f8..8de3cc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "MIT", "dependencies": { "@hono/sentry": "^1.0.1", - "hono": "^3.12.12" + "hono": "^3.12.12", + "i18next": "^23.8.2" }, "devDependencies": { "@cloudflare/workers-types": "^4.20240423.0", @@ -26,7 +27,6 @@ "eslint-config-typescript": "^3.0.0", "eslint-plugin-optimize-regex": "^1.2.1", "eslint-plugin-sonarjs": "^0.25.1", - "i18next": "^23.8.2", "jest": "^29.7.0", "jest-environment-miniflare": "^2.14.2", "prettier": "^3.2.5", @@ -487,7 +487,6 @@ "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -562,70 +561,6 @@ "node": ">=16.13" } }, - "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20240419.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240419.0.tgz", - "integrity": "sha512-PGVe9sYWULHfvGhN0IZh8MsskNG/ufnBSqPbgFCxJHCTrVXLPuC35EoVaforyqjKRwj3U35XMyGo9KHcGnTeHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20240419.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240419.0.tgz", - "integrity": "sha512-z4etQSPiD5Gcjs962LiC7ZdmXnN6SGof5KrYoFiSI9X9kUvpuGH/lnjVVPd+NnVNeDU2kzmcAIgyZjkjTaqVXQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20240419.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240419.0.tgz", - "integrity": "sha512-lBwhg0j3sYTFMsEb4bOClbVje8nqrYOu0H3feQlX+Eks94JIhWPkf8ywK4at/BUc1comPMhCgzDHwc2OMPUGgg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20240419.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240419.0.tgz", - "integrity": "sha512-ZMY6wwWkxL+WPq8ydOp/irSYjAnMhBz1OC1+4z+OANtDs2beaZODmq7LEB3hb5WUAaTPY7DIjZh3DfDfty0nYg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, "node_modules/@cloudflare/workerd-windows-64": { "version": "1.20240419.0", "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240419.0.tgz", @@ -2093,105 +2028,6 @@ "@sentry/cli-win32-x64": "2.31.0" } }, - "node_modules/@sentry/cli-darwin": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.31.0.tgz", - "integrity": "sha512-VM5liyxMnm4K2g0WsrRPXRCMLhaT09C7gK5Fz/CxKYh9sbMZB7KA4hV/3klkyuyw1+ECF1J66cefhNkFZepUig==", - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-linux-arm": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.31.0.tgz", - "integrity": "sha512-AZoCN3waXEfXGCd3YSrikcX/y63oQe0Tiyapkeoifq/0QhI+2MOOrAQb60gthsXwb0UDK/XeFi3PaxyUCphzxA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-linux-arm64": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.31.0.tgz", - "integrity": "sha512-eENJTmXoFX3uNr8xRW7Bua2Sw3V1tylQfdtS85pNjZPdbm3U8wYQSWu2VoZkK2ASOoC+17YC8jTQxq62KWnSeQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-linux-i686": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.31.0.tgz", - "integrity": "sha512-cQUFb3brhLaNSIoNzjU/YASnTM1I3TDJP9XXzH0eLK9sSopCcDcc6OrYEYvdjJXZKzFv5sbc9UNMsIDbh4+rYg==", - "cpu": [ - "x86", - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-linux-x64": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.31.0.tgz", - "integrity": "sha512-z1zTNg91nZJRdcGHC/bCU1KwIaifV0MLJteip9KrFDprzhJk1HtMxFOS0+OZ5/UH21CjAFmg9Pj6IAGqm3BYjA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-win32-i686": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.31.0.tgz", - "integrity": "sha512-+K7fdk57aUd4CmYrQfDGYPzVyxsTnVro6IPb5QSSLpP03dL7ko5208epu4m2SyN/MkFvscy9Di3n3DTvIfDU2w==", - "cpu": [ - "x86", - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, "node_modules/@sentry/cli-win32-x64": { "version": "2.31.0", "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.31.0.tgz", @@ -4491,20 +4327,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4728,7 +4550,6 @@ "version": "23.8.2", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.8.2.tgz", "integrity": "sha512-Z84zyEangrlERm0ZugVy4bIt485e/H8VecGUZkZWrH7BDePG6jT73QdL9EA1tRTTVVMpry/MgWIP1FjEn0DRXA==", - "dev": true, "funding": [ { "type": "individual", @@ -7837,8 +7658,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regexp-tree": { "version": "0.1.27", diff --git a/package.json b/package.json index 17c3df9..9a7db4d 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "eslint-config-typescript": "^3.0.0", "eslint-plugin-optimize-regex": "^1.2.1", "eslint-plugin-sonarjs": "^0.25.1", - "i18next": "^23.8.2", "jest": "^29.7.0", "jest-environment-miniflare": "^2.14.2", "prettier": "^3.2.5", @@ -40,6 +39,7 @@ }, "dependencies": { "@hono/sentry": "^1.0.1", + "i18next": "^23.8.2", "hono": "^3.12.12" } } diff --git a/src/embed/status.ts b/src/embed/status.ts index 4205a92..58b537d 100644 --- a/src/embed/status.ts +++ b/src/embed/status.ts @@ -9,6 +9,8 @@ import { renderVideo } from '../render/video'; import { renderInstantView } from '../render/instantview'; import { constructTwitterThread } from '../providers/twitter/conversation'; import { Experiment, experimentCheck } from '../experiments'; +import i18next from 'i18next'; +import translationResources from '../../i18n/resources.json'; export const returnError = (c: Context, error: string): Response => { return c.html( @@ -126,6 +128,13 @@ export const handleStatus = async ( let overrideMedia: APIMedia | undefined; + await i18next.init({ + lng: language ?? status.lang ?? 'en', + debug: true, + resources: translationResources, + fallbackLng: 'en' + }); + // Check if mediaNumber exists, and if that media exists in status.media.all. If it does, we'll store overrideMedia variable if (mediaNumber && status.media && status.media.all && status.media.all[mediaNumber - 1]) { overrideMedia = status.media.all[mediaNumber - 1]; @@ -218,15 +227,11 @@ export const handleStatus = async ( if (status.translation) { const { translation } = status; - const formatText = - language === 'en' - ? Strings.TRANSLATE_TEXT.format({ - language: translation.source_lang_en - }) - : Strings.TRANSLATE_TEXT_INTL.format({ - source: translation.source_lang.toUpperCase(), - destination: translation.target_lang.toUpperCase() - }); + const formatText = `๐Ÿ“‘ {translation}`.format({ + translation: i18next.t('translatedFrom').format({ + language: i18next.t(`language_${translation.source_lang}`) + }) + }); newText = `${formatText}\n\n` + `${translation.text}\n\n`; } @@ -380,7 +385,10 @@ export const handleStatus = async ( }); /* Finally, add the footer of the poll with # of votes and time left */ - str += `\n${formatNumber(poll.total_votes)} votes ยท ${poll.time_left_en}`; + str += '\n'; /* TODO: Localize time left */ + str += i18next + .t('pollVotes') + .format({ voteCount: formatNumber(poll.total_votes), timeLeft: poll.time_left_en }); /* Check if the poll is ongoing and apply low TTL cache control. Yes, checking if this is a string is a hacky way to do this, but @@ -454,13 +462,13 @@ export const handleStatus = async ( /* Special reply handling if authorText is not overriden */ if (status.replying_to && authorText === Strings.DEFAULT_AUTHOR_TEXT) { - authorText = `โ†ช Replying to @${status.replying_to.screen_name}`; + authorText = `โ†ช ${i18next.t('replyingTo').format({ screen_name: status.replying_to.screen_name })}`; /* We'll assume it's a thread if it's a reply to themselves */ } else if ( status.replying_to?.screen_name === status.author.screen_name && authorText === Strings.DEFAULT_AUTHOR_TEXT ) { - authorText = `โ†ช A part of @${status.author.screen_name}'s thread`; + authorText = `โ†ช ${i18next.t('threadPartHeader').format({ screen_name: status.author.screen_name })}`; } if (!flags.gallery) { diff --git a/src/providers/twitter/processor.ts b/src/providers/twitter/processor.ts index 05a721e..f23ebf5 100644 --- a/src/providers/twitter/processor.ts +++ b/src/providers/twitter/processor.ts @@ -150,8 +150,8 @@ export const buildAPITwitterStatus = async ( apiStatus.replying_to_status = status.legacy?.in_reply_to_status_id_str || null; } else if (status.legacy.in_reply_to_screen_name) { apiStatus.replying_to = { - screen_name: status.legacy.in_reply_to_screen_name || null, - post: status.legacy.in_reply_to_status_id_str || null + screen_name: status.legacy.in_reply_to_screen_name, + post: status.legacy.in_reply_to_status_id_str }; } else { apiStatus.replying_to = null; diff --git a/src/render/instantview.ts b/src/render/instantview.ts index c42dda4..4723905 100644 --- a/src/render/instantview.ts +++ b/src/render/instantview.ts @@ -1,8 +1,8 @@ /* eslint-disable no-irregular-whitespace */ +import i18next from 'i18next'; import { Constants } from '../constants'; import { getSocialTextIV } from '../helpers/socialproof'; import { sanitizeText } from '../helpers/utils'; -import { Strings } from '../strings'; enum AuthorActionType { Reply = 'Reply', @@ -36,10 +36,10 @@ const generateStatusMedia = (status: APIStatus, author: APIUser): string => { }); break; case 'video': - media += `