diff --git a/i18n/resources.json b/i18n/resources.json index d5f1012..e896cbe 100644 --- a/i18n/resources.json +++ b/i18n/resources.json @@ -5,6 +5,9 @@ "quotedFrom": "Quoting {name} (@{screen_name})", "replyingTo": "Replying to @{screen_name}", "threadPartHeader": "A part of @${status.author.screen_name}'s thread", + "ivAuthorActionReply": "Reply from {authorName} (@{authorScreenName}):", + "ivAuthorActionOriginal": "Original from {authorName} (@{authorScreenName}):", + "ivAuthorActionFollowUp": "Follow-up from {authorName} (@{authorScreenName}):", "photoCount": "Photo {number} / {total}", "videoCount": "Video {number} / {total}", @@ -15,19 +18,21 @@ "ivOriginalText": "Original text", "ivViewOriginal": "View full thread", - "ivViewOriginalStatus": "View full thread", "ivAboutAuthor": "About author", - "ivProfileFollowing": "Following", - "ivProfileFollowers": "{numFollowers, plural, one {# Follower} other {# Followers}}", - "ivProfileStatuses": "{numPosts, plural, one {# Post} other {# Posts}}", + "ivProfileFollowing": "{numFollowing, plural, one {Following} other {Following}}", + "ivProfileFollowers": "{numFollowers, plural, one {Follower} other {Followers}}", + "ivProfileStatuses": "{numPosts, plural, one {Post} other {Posts}}", + "ivProfilePictureAlt": "{author}'s profile picture", "ivFallbackText": "If you can see this, your browser is doing something weird with your user agent.", "ivInternetArchiveText": "{brandingName} archive", "pollFinalResults": "Final results", "pollVotes": "{voteCount, plural, one {# vote} other {# votes}} ยท {timeLeft}", + "ivPollChoice": "{voteCount, plural, one {# vote} other {# votes}}, {percentage}%", "ivQuoteHeader": "Quoting {authorName} (@{authorHandle})", + "ivCommunityNoteHeader": "Readers added context they thought people might want to know", "language_af": "Afrikaans", "language_ar": "Arabic", diff --git a/package-lock.json b/package-lock.json index 8de3cc5..404b1c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "@hono/sentry": "^1.0.1", "hono": "^3.12.12", - "i18next": "^23.8.2" + "i18next": "^23.8.2", + "i18next-icu": "^2.3.0" }, "devDependencies": { "@cloudflare/workers-types": "^4.20240423.0", @@ -769,6 +770,55 @@ "node": ">=14" } }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz", + "integrity": "sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==", + "peer": true, + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", + "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.6.tgz", + "integrity": "sha512-etVau26po9+eewJKYoiBKP6743I1br0/Ie00Pb/S/PtmYfmjTcOn2YCh2yNkSZI12h6Rg+BOgQYborXk46BvkA==", + "peer": true, + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/icu-skeleton-parser": "1.8.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.0.tgz", + "integrity": "sha512-QWLAYvM0n8hv7Nq5BEs4LKIjevpVpbGLAJgOaYzg9wABEoX1j0JO1q2/jVkO6CVlq0dbsxZCngS5aXbysYueqA==", + "peer": true, + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@hono/sentry": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@hono/sentry/-/sentry-1.0.1.tgz", @@ -4568,6 +4618,14 @@ "@babel/runtime": "^7.23.2" } }, + "node_modules/i18next-icu": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/i18next-icu/-/i18next-icu-2.3.0.tgz", + "integrity": "sha512-x+j7kd5nDJCfbU53uwsMfXD7ALPu5uv0bqjAMQ5nVvXRoj1L7gkmswKtM3XDWYo4YUHf1jznlhSdPyy0xEwU+Q==", + "peerDependencies": { + "intl-messageformat": "^10.3.3" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -4642,6 +4700,18 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/intl-messageformat": { + "version": "10.5.11", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.11.tgz", + "integrity": "sha512-eYq5fkFBVxc7GIFDzpFQkDOZgNayNTQn4Oufe8jw6YY6OHVw70/4pA3FyCsQ0Gb2DnvEJEMmN2tOaXUGByM+kg==", + "peer": true, + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.6", + "tslib": "^2.4.0" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -8647,8 +8717,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/type-check": { "version": "0.4.0", diff --git a/package.json b/package.json index 9a7db4d..f987eb8 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ }, "dependencies": { "@hono/sentry": "^1.0.1", + "hono": "^3.12.12", "i18next": "^23.8.2", - "hono": "^3.12.12" + "i18next-icu": "^2.3.0" } } diff --git a/src/constants.ts b/src/constants.ts index 2426cab..c5cd8ad 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -15,7 +15,7 @@ export const Constants = { REDIRECT_URL: REDIRECT_URL, RELEASE_NAME: RELEASE_NAME, GIF_TRANSCODE_DOMAIN: GIF_TRANSCODE_DOMAIN, - API_DOCS_URL: `https://github.com/dangeredwolf/FixTweet/wiki/API-Home`, + API_DOCS_URL: `https://github.com/FixTweet/FxTwitter/wiki/API-Home`, TWITTER_ROOT: 'https://twitter.com', TWITTER_GLOBAL_NAME_ROOT: 'twitter.com', TWITTER_API_ROOT: 'https://api.twitter.com', diff --git a/src/embed/status.ts b/src/embed/status.ts index 58b537d..cfe2e8a 100644 --- a/src/embed/status.ts +++ b/src/embed/status.ts @@ -10,6 +10,7 @@ import { renderInstantView } from '../render/instantview'; import { constructTwitterThread } from '../providers/twitter/conversation'; import { Experiment, experimentCheck } from '../experiments'; import i18next from 'i18next'; +import icu from "i18next-icu"; import translationResources from '../../i18n/resources.json'; export const returnError = (c: Context, error: string): Response => { @@ -128,7 +129,7 @@ export const handleStatus = async ( let overrideMedia: APIMedia | undefined; - await i18next.init({ + await i18next.use(icu).init({ lng: language ?? status.lang ?? 'en', debug: true, resources: translationResources, diff --git a/src/render/instantview.ts b/src/render/instantview.ts index 37bf87f..2acb387 100644 --- a/src/render/instantview.ts +++ b/src/render/instantview.ts @@ -10,7 +10,7 @@ enum AuthorActionType { FollowUp = 'FollowUp' } -const populateUserLinks = (status: APIStatus, text: string): string => { +const populateUserLinks = (text: string): string => { /* TODO: Maybe we can add username splices to our API so only genuinely valid users are linked? */ text.match(/@(\w{1,15})/g)?.forEach(match => { const username = match.replace('@', ''); @@ -22,7 +22,7 @@ const populateUserLinks = (status: APIStatus, text: string): string => { return text; }; -const generateStatusMedia = (status: APIStatus, author: APIUser): string => { +const generateStatusMedia = (status: APIStatus): string => { let media = ''; if (status.media?.all?.length) { status.media.all.forEach(mediaItem => { @@ -95,7 +95,7 @@ function getTranslatedText(status: APITwitterStatus, isQuote = false): string | let text = paragraphify(sanitizeText(status.translation?.text), isQuote); text = htmlifyLinks(text); text = htmlifyHashtags(text); - text = populateUserLinks(status, text); + text = populateUserLinks(text); const formatText = `๐ {translation}`.format({ translation: i18next.t('translatedFrom').format({ @@ -124,16 +124,28 @@ const generateInlineAuthorHeader = ( author: APIUser, authorActionType: AuthorActionType | null ): string => { - return `
{socialText}
@@ -168,7 +180,7 @@ const generateStatusFooter = (status: APIStatus, isQuote = false, author: APIUse `.format({ socialText: getSocialTextIV(status as APITwitterStatus) || '', viewOriginal: !isQuote - ? `${i18next.t('ivViewOriginalStatus')}` + ? `${i18next.t('ivViewOriginal')}` : notApplicableComment, aboutSection: isQuote ? '' @@ -179,13 +191,11 @@ const generateStatusFooter = (status: APIStatus, isQuote = false, author: APIUse${description}
{location} {website} {joined}
- {following} ${i18next.t('ivProfileFollowing')}โ - {followers} ${i18next.t('ivProfileFollowers')}โ - {statuses} ${i18next.t('ivProfileStatuses')} + {following} ${i18next.t('ivProfileFollowing', { numFollowing: author.following })}โ + {followers} ${i18next.t('ivProfileFollowers', { numFollowers: author.followers })}โ + {statuses} ${i18next.t('ivProfileStatuses', { numStatuses: author.statuses })}
`.format({ - pfp: `Readers added context they thought people might want to know | +${i18next.t('ivCommunityNoteHeader')} | ${result.replace(/\n/g, '\n ')} |
@@ -273,7 +283,7 @@ const generateStatus = (
return `
{quoteHeader}
- ${generateStatusMedia(status, author)}
+ ${generateStatusMedia(status)}
${translatedText ? translatedText : notApplicableComment}
@@ -286,7 +296,7 @@ const generateStatus = (
${status.poll ? generatePoll(status.poll, status.lang ?? 'en') : notApplicableComment}
${!isQuote && status.quote ? generateStatus(status.quote, author, true, null) : notApplicableComment}
-
---|