Add i18n for most visible strings

This commit is contained in:
dangered wolf 2024-02-09 18:58:56 -05:00
parent 0b26e72296
commit 4ee8ae9e9a
No known key found for this signature in database
GPG key ID: 41E4D37680ED8B58
7 changed files with 117 additions and 223 deletions

59
i18n/resources.json Normal file
View file

@ -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": "<a href=\"{url}\">Quoting</a> {authorName} (<a href=\"{authorURL}\">@{authorHandle}</a>)",
"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"
}
}
}

186
package-lock.json generated
View file

@ -10,7 +10,8 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@hono/sentry": "^1.0.1", "@hono/sentry": "^1.0.1",
"hono": "^3.12.12" "hono": "^3.12.12",
"i18next": "^23.8.2"
}, },
"devDependencies": { "devDependencies": {
"@cloudflare/workers-types": "^4.20240423.0", "@cloudflare/workers-types": "^4.20240423.0",
@ -26,7 +27,6 @@
"eslint-config-typescript": "^3.0.0", "eslint-config-typescript": "^3.0.0",
"eslint-plugin-optimize-regex": "^1.2.1", "eslint-plugin-optimize-regex": "^1.2.1",
"eslint-plugin-sonarjs": "^0.25.1", "eslint-plugin-sonarjs": "^0.25.1",
"i18next": "^23.8.2",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-environment-miniflare": "^2.14.2", "jest-environment-miniflare": "^2.14.2",
"prettier": "^3.2.5", "prettier": "^3.2.5",
@ -487,7 +487,6 @@
"version": "7.23.9", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
"integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
"dev": true,
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
}, },
@ -562,70 +561,6 @@
"node": ">=16.13" "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": { "node_modules/@cloudflare/workerd-windows-64": {
"version": "1.20240419.0", "version": "1.20240419.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240419.0.tgz", "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" "@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": { "node_modules/@sentry/cli-win32-x64": {
"version": "2.31.0", "version": "2.31.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.31.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.31.0.tgz",
@ -4491,20 +4327,6 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true "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": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@ -4728,7 +4550,6 @@
"version": "23.8.2", "version": "23.8.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.8.2.tgz", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.8.2.tgz",
"integrity": "sha512-Z84zyEangrlERm0ZugVy4bIt485e/H8VecGUZkZWrH7BDePG6jT73QdL9EA1tRTTVVMpry/MgWIP1FjEn0DRXA==", "integrity": "sha512-Z84zyEangrlERm0ZugVy4bIt485e/H8VecGUZkZWrH7BDePG6jT73QdL9EA1tRTTVVMpry/MgWIP1FjEn0DRXA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -7837,8 +7658,7 @@
"node_modules/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.14.1", "version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
"dev": true
}, },
"node_modules/regexp-tree": { "node_modules/regexp-tree": {
"version": "0.1.27", "version": "0.1.27",

View file

@ -29,7 +29,6 @@
"eslint-config-typescript": "^3.0.0", "eslint-config-typescript": "^3.0.0",
"eslint-plugin-optimize-regex": "^1.2.1", "eslint-plugin-optimize-regex": "^1.2.1",
"eslint-plugin-sonarjs": "^0.25.1", "eslint-plugin-sonarjs": "^0.25.1",
"i18next": "^23.8.2",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-environment-miniflare": "^2.14.2", "jest-environment-miniflare": "^2.14.2",
"prettier": "^3.2.5", "prettier": "^3.2.5",
@ -40,6 +39,7 @@
}, },
"dependencies": { "dependencies": {
"@hono/sentry": "^1.0.1", "@hono/sentry": "^1.0.1",
"i18next": "^23.8.2",
"hono": "^3.12.12" "hono": "^3.12.12"
} }
} }

View file

@ -9,6 +9,8 @@ import { renderVideo } from '../render/video';
import { renderInstantView } from '../render/instantview'; import { renderInstantView } from '../render/instantview';
import { constructTwitterThread } from '../providers/twitter/conversation'; import { constructTwitterThread } from '../providers/twitter/conversation';
import { Experiment, experimentCheck } from '../experiments'; import { Experiment, experimentCheck } from '../experiments';
import i18next from 'i18next';
import translationResources from '../../i18n/resources.json';
export const returnError = (c: Context, error: string): Response => { export const returnError = (c: Context, error: string): Response => {
return c.html( return c.html(
@ -126,6 +128,13 @@ export const handleStatus = async (
let overrideMedia: APIMedia | undefined; 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 // 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]) { if (mediaNumber && status.media && status.media.all && status.media.all[mediaNumber - 1]) {
overrideMedia = status.media.all[mediaNumber - 1]; overrideMedia = status.media.all[mediaNumber - 1];
@ -218,15 +227,11 @@ export const handleStatus = async (
if (status.translation) { if (status.translation) {
const { translation } = status; const { translation } = status;
const formatText = const formatText = `📑 {translation}`.format({
language === 'en' translation: i18next.t('translatedFrom').format({
? Strings.TRANSLATE_TEXT.format({ language: i18next.t(`language_${translation.source_lang}`)
language: translation.source_lang_en })
}) });
: Strings.TRANSLATE_TEXT_INTL.format({
source: translation.source_lang.toUpperCase(),
destination: translation.target_lang.toUpperCase()
});
newText = `${formatText}\n\n` + `${translation.text}\n\n`; 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 */ /* 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. /* 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 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 */ /* Special reply handling if authorText is not overriden */
if (status.replying_to && authorText === Strings.DEFAULT_AUTHOR_TEXT) { 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 */ /* We'll assume it's a thread if it's a reply to themselves */
} else if ( } else if (
status.replying_to?.screen_name === status.author.screen_name && status.replying_to?.screen_name === status.author.screen_name &&
authorText === Strings.DEFAULT_AUTHOR_TEXT 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) { if (!flags.gallery) {

View file

@ -150,8 +150,8 @@ export const buildAPITwitterStatus = async (
apiStatus.replying_to_status = status.legacy?.in_reply_to_status_id_str || null; apiStatus.replying_to_status = status.legacy?.in_reply_to_status_id_str || null;
} else if (status.legacy.in_reply_to_screen_name) { } else if (status.legacy.in_reply_to_screen_name) {
apiStatus.replying_to = { apiStatus.replying_to = {
screen_name: status.legacy.in_reply_to_screen_name || null, screen_name: status.legacy.in_reply_to_screen_name,
post: status.legacy.in_reply_to_status_id_str || null post: status.legacy.in_reply_to_status_id_str
}; };
} else { } else {
apiStatus.replying_to = null; apiStatus.replying_to = null;

View file

@ -1,8 +1,8 @@
/* eslint-disable no-irregular-whitespace */ /* eslint-disable no-irregular-whitespace */
import i18next from 'i18next';
import { Constants } from '../constants'; import { Constants } from '../constants';
import { getSocialTextIV } from '../helpers/socialproof'; import { getSocialTextIV } from '../helpers/socialproof';
import { sanitizeText } from '../helpers/utils'; import { sanitizeText } from '../helpers/utils';
import { Strings } from '../strings';
enum AuthorActionType { enum AuthorActionType {
Reply = 'Reply', Reply = 'Reply',
@ -36,10 +36,10 @@ const generateStatusMedia = (status: APIStatus, author: APIUser): string => {
}); });
break; break;
case 'video': case 'video':
media += `<video src="${mediaItem.url}" alt="${author.name}'s video. Alt text not available."/>`; media += `<video src="${mediaItem.url}" alt="${i18next.t('videoAltTextUnavailable').format({ author: status.author.name })}"/>`;
break; break;
case 'gif': case 'gif':
media += `<video src="${mediaItem.url}" alt="${author.name}'s gif. Alt text not available."/>`; media += `<video src="${mediaItem.url}" alt="${i18next.t('gifAltTextUnavailable').format({ author: status.author.name })}"/>`;
break; break;
} }
}); });
@ -97,17 +97,13 @@ function getTranslatedText(status: APITwitterStatus, isQuote = false): string |
text = htmlifyHashtags(text); text = htmlifyHashtags(text);
text = populateUserLinks(status, text); text = populateUserLinks(status, text);
const formatText = const formatText = `📑 {translation}`.format({
status.translation.target_lang === 'en' translation: i18next.t('translatedFrom').format({
? Strings.TRANSLATE_TEXT.format({ language: i18next.t(`language_${status.translation.source_lang}`)
language: status.translation.source_lang_en })
}) });
: Strings.TRANSLATE_TEXT_INTL.format({
source: status.translation.source_lang.toUpperCase(),
destination: status.translation.target_lang.toUpperCase()
});
return `<h4>${formatText}</h4>${text}<h4>Original</h4>`; return `<h4>${formatText}</h4>${text}<h4>${i18next.t('ivOriginalText')}</h4>`;
} }
const notApplicableComment = '<!-- N/A -->'; const notApplicableComment = '<!-- N/A -->';
@ -171,19 +167,21 @@ const generateStatusFooter = (status: APIStatus, isQuote = false, author: APIUse
{aboutSection} {aboutSection}
`.format({ `.format({
socialText: getSocialTextIV(status as APITwitterStatus) || '', socialText: getSocialTextIV(status as APITwitterStatus) || '',
viewOriginal: !isQuote ? `<a href="${status.url}">View full thread</a>` : notApplicableComment, viewOriginal: !isQuote
? `<a href="${status.url}">${i18next.t('ivViewOriginalStatus')}</a>`
: notApplicableComment,
aboutSection: isQuote aboutSection: isQuote
? '' ? ''
: `<h2>About author</h2> : `<h2>${i18next.t('ivAboutAuthor')}</h2>
{pfp} {pfp}
<h2>${author.name}</h2> <h2>${author.name}</h2>
<p><a href="${author.url}">@${author.screen_name}</a></p> <p><a href="${author.url}">@${author.screen_name}</a></p>
<p><b>${description}</b></p> <p><b>${description}</b></p>
<p>{location} {website} {joined}</p> <p>{location} {website} {joined}</p>
<p> <p>
{following} <b>Following</b> {following} <b>${i18next.t('ivProfileFollowing')}</b>
{followers} <b>Followers</b> {followers} <b>${i18next.t('ivProfileFollowers')}</b>
{statuses} <b>Posts</b> {statuses} <b>${i18next.t('ivProfileStatuses')}</b>
</p>`.format({ </p>`.format({
pfp: `<img src="${author.avatar_url?.replace('_200x200', '_400x400')}" alt="${ pfp: `<img src="${author.avatar_url?.replace('_200x200', '_400x400')}" alt="${
author.name author.name
@ -288,9 +286,18 @@ const generateStatus = (
${status.poll ? generatePoll(status.poll, status.lang ?? 'en') : notApplicableComment} ${status.poll ? generatePoll(status.poll, status.lang ?? 'en') : notApplicableComment}
<!-- Embedded quote status --> <!-- Embedded quote status -->
${!isQuote && status.quote ? generateStatus(status.quote, author, true, null) : notApplicableComment} ${!isQuote && status.quote ? generateStatus(status.quote, author, true, null) : notApplicableComment}
${!isQuote ? generateStatusFooter(status, true, author) : ''}
<br>${!isQuote ? `<a href="${status.url}">${i18next.t('ivViewOriginalStatus')}</a>` : notApplicableComment}
`.format({ `.format({
quoteHeader: isQuote quoteHeader: isQuote
? `<h4><a href="${status.url}">Quoting</a> ${author.name} (<a href="${Constants.TWITTER_ROOT}/${author.screen_name}">@${author.screen_name}</a>)</h4>` ? '<h4>' +
i18next.t('ivQuoteHeader').format({
url: status.url,
authorName: status.author.name,
authorHandle: status.author.screen_name,
authorURL: `${Constants.TWITTER_ROOT}/${status.author.screen_name}`
}) +
'</h4>'
: '' : ''
}); });
}; };
@ -329,12 +336,12 @@ export const renderInstantView = (properties: RenderProperties): ResponseInstruc
</section> </section>
<section class="section--first">${ <section class="section--first">${
flags?.archive flags?.archive
? `${Constants.BRANDING_NAME} archive` ? i18next.t('ivInternetArchiveText').format({ brandingName: Constants.BRANDING_NAME })
: 'If you can see this, your browser is doing something weird with your user agent.' : i18next.t('ivFallbackText')
} <a href="${status.url}">View full thread</a> } <a href="${status.url}">${i18next.t('ivViewOriginalStatus')}</a>
</section> </section>
<article> <article>
<sub><a href="${status.url}">View full thread</a></sub> <sub><a href="${status.url}">${i18next.t('ivViewOriginal')}</a></sub>
<h1>${status.author.name} (@${status.author.screen_name})</h1> <h1>${status.author.name} (@${status.author.screen_name})</h1>
${thread?.thread ${thread?.thread

View file

@ -141,8 +141,8 @@ interface APIStatus {
possibly_sensitive: boolean; possibly_sensitive: boolean;
replying_to: { replying_to: {
screen_name: string | null; screen_name: string;
post: string | null; post: string;
} | null; } | null;
source: string | null; source: string | null;