From 945d2ac20677fa8eb1be15bf20e0a65ac10ec456 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 1 Mar 2025 17:52:27 +0800 Subject: [PATCH] Experiment using browser's built in translation API Spec: https://webmachinelearning.github.io/translation-api/ API may change in the future --- src/components/compose.jsx | 7 + src/components/status.jsx | 24 +- src/components/translation-block.jsx | 21 +- src/locales/en.po | 476 +++++++++++++-------------- src/utils/browser-translator.js | 107 ++++++ 5 files changed, 395 insertions(+), 240 deletions(-) create mode 100644 src/utils/browser-translator.js diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 913785a1..4a9ed6d2 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -28,6 +28,7 @@ import Menu2 from '../components/menu2'; import supportedLanguages from '../data/status-supported-languages'; import urlRegex from '../data/url-regex'; import { api } from '../utils/api'; +import { langDetector } from '../utils/browser-translator'; import db from '../utils/db'; import emojifyText from '../utils/emojify-text'; import i18nDuration from '../utils/i18n-duration'; @@ -1875,6 +1876,12 @@ const getCustomEmojis = pmem(_getCustomEmojis, { }); const detectLangs = async (text) => { + if (langDetector) { + const langs = await langDetector.detect(text); + if (langs?.length) { + return langs.slice(0, 2).map((lang) => lang.detectedLanguage); + } + } const { detectAll } = await import('tinyld/light'); const langs = detectAll(text); if (langs?.length) { diff --git a/src/components/status.jsx b/src/components/status.jsx index 3d8e0142..f75f6e98 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -41,6 +41,7 @@ import Modal from '../components/modal'; import NameText from '../components/name-text'; import Poll from '../components/poll'; import { api } from '../utils/api'; +import { langDetector } from '../utils/browser-translator'; import emojifyText from '../utils/emojify-text'; import enhanceContent from '../utils/enhance-content'; import FilterContext from '../utils/filter-context'; @@ -253,7 +254,6 @@ const SIZE_CLASS = { }; const detectLang = pmem(async (text) => { - const { detectAll } = await import('tinyld/light'); text = text?.trim(); // Ref: https://github.com/komodojp/tinyld/blob/develop/docs/benchmark.md @@ -261,7 +261,29 @@ const detectLang = pmem(async (text) => { if (text?.length > 500) { return null; } + + if (langDetector) { + const langs = await langDetector.detect(text); + console.groupCollapsed( + '💬 DETECTLANG BROWSER', + langs.slice(0, 3).map((l) => l.detectedLanguage), + ); + console.log(text, langs.slice(0, 3)); + console.groupEnd(); + const lang = langs[0]; + if (lang?.detectedLanguage && lang?.confidence > 0.5) { + return lang.detectedLanguage; + } + } + + const { detectAll } = await import('tinyld/light'); const langs = detectAll(text); + console.groupCollapsed( + '💬 DETECTLANG TINYLD', + langs.slice(0, 3).map((l) => l.lang), + ); + console.log(text, langs.slice(0, 3)); + console.groupEnd(); const lang = langs[0]; if (lang?.lang && lang?.accuracy > 0.5) { // If > 50% accurate, use it diff --git a/src/components/translation-block.jsx b/src/components/translation-block.jsx index 65936838..c1081818 100644 --- a/src/components/translation-block.jsx +++ b/src/components/translation-block.jsx @@ -6,6 +6,10 @@ import pThrottle from 'p-throttle'; import { useEffect, useRef, useState } from 'preact/hooks'; import sourceLanguages from '../data/lingva-source-languages'; +import { + translate as browserTranslate, + supportsBrowserTranslator, +} from '../utils/browser-translator'; import getTranslateTargetLanguage from '../utils/get-translate-target-language'; import localeCode2Text from '../utils/localeCode2Text'; import pmem from '../utils/pmem'; @@ -95,7 +99,22 @@ function TranslationBlock({ const apiSourceLang = useRef('auto'); if (!onTranslate) { - onTranslate = mini ? throttledLingvaTranslate : lingvaTranslate; + // onTranslate = supportsBrowserTranslator + // ? browserTranslate + // : mini + // ? throttledLingvaTranslate + // : lingvaTranslate; + onTranslate = async (...args) => { + if (supportsBrowserTranslator) { + const result = await browserTranslate(...args); + if (result && !result.error) { + return result; + } + } + return mini + ? await throttledLingvaTranslate(...args) + : await lingvaTranslate(...args); + }; } const translate = async () => { diff --git a/src/locales/en.po b/src/locales/en.po index 4326f6b6..23f81e99 100644 --- a/src/locales/en.po +++ b/src/locales/en.po @@ -34,7 +34,7 @@ msgstr "" #: src/components/account-block.jsx:169 #: src/components/account-info.jsx:664 -#: src/components/status.jsx:525 +#: src/components/status.jsx:547 msgid "Group" msgstr "" @@ -108,14 +108,14 @@ msgstr "" #: src/components/account-info.jsx:430 #: src/components/account-info.jsx:1143 -#: src/components/compose.jsx:2696 +#: src/components/compose.jsx:2703 #: src/components/media-alt-modal.jsx:46 #: src/components/media-modal.jsx:358 -#: src/components/status.jsx:1748 -#: src/components/status.jsx:1765 -#: src/components/status.jsx:1890 -#: src/components/status.jsx:2495 -#: src/components/status.jsx:2498 +#: src/components/status.jsx:1770 +#: src/components/status.jsx:1787 +#: src/components/status.jsx:1912 +#: src/components/status.jsx:2517 +#: src/components/status.jsx:2520 #: src/pages/account-statuses.jsx:523 #: src/pages/accounts.jsx:110 #: src/pages/hashtag.jsx:200 @@ -197,7 +197,7 @@ msgid "Original" msgstr "" #: src/components/account-info.jsx:887 -#: src/components/status.jsx:2281 +#: src/components/status.jsx:2303 #: src/pages/catchup.jsx:71 #: src/pages/catchup.jsx:1445 #: src/pages/catchup.jsx:2058 @@ -294,30 +294,30 @@ msgid "Add/Remove from Lists" msgstr "" #: src/components/account-info.jsx:1327 -#: src/components/status.jsx:1188 +#: src/components/status.jsx:1210 msgid "Link copied" msgstr "" #: src/components/account-info.jsx:1330 -#: src/components/status.jsx:1191 +#: src/components/status.jsx:1213 msgid "Unable to copy link" msgstr "" #: src/components/account-info.jsx:1336 #: src/components/shortcuts-settings.jsx:1059 -#: src/components/status.jsx:1197 -#: src/components/status.jsx:3274 +#: src/components/status.jsx:1219 +#: src/components/status.jsx:3296 msgid "Copy" msgstr "" #: src/components/account-info.jsx:1351 #: src/components/shortcuts-settings.jsx:1077 -#: src/components/status.jsx:1213 +#: src/components/status.jsx:1235 msgid "Sharing doesn't seem to work." msgstr "" #: src/components/account-info.jsx:1357 -#: src/components/status.jsx:1219 +#: src/components/status.jsx:1241 msgid "Share…" msgstr "" @@ -419,11 +419,11 @@ msgstr "" #: src/components/account-info.jsx:2020 #: src/components/account-info.jsx:2140 #: src/components/account-sheet.jsx:38 -#: src/components/compose.jsx:876 -#: src/components/compose.jsx:2652 -#: src/components/compose.jsx:3126 -#: src/components/compose.jsx:3335 -#: src/components/compose.jsx:3565 +#: src/components/compose.jsx:877 +#: src/components/compose.jsx:2659 +#: src/components/compose.jsx:3133 +#: src/components/compose.jsx:3342 +#: src/components/compose.jsx:3572 #: src/components/drafts.jsx:59 #: src/components/embed-modal.jsx:13 #: src/components/generic-accounts.jsx:143 @@ -436,9 +436,9 @@ msgstr "" #: src/components/shortcuts-settings.jsx:230 #: src/components/shortcuts-settings.jsx:583 #: src/components/shortcuts-settings.jsx:783 -#: src/components/status.jsx:2998 -#: src/components/status.jsx:3238 -#: src/components/status.jsx:3738 +#: src/components/status.jsx:3020 +#: src/components/status.jsx:3260 +#: src/components/status.jsx:3760 #: src/pages/accounts.jsx:37 #: src/pages/catchup.jsx:1581 #: src/pages/filters.jsx:224 @@ -570,222 +570,222 @@ msgstr "" msgid "Compose" msgstr "" -#: src/components/compose.jsx:210 +#: src/components/compose.jsx:211 msgid "Add media" msgstr "Add media" -#: src/components/compose.jsx:211 +#: src/components/compose.jsx:212 msgid "Add custom emoji" msgstr "" -#: src/components/compose.jsx:212 +#: src/components/compose.jsx:213 msgid "Add GIF" msgstr "Add GIF" -#: src/components/compose.jsx:213 +#: src/components/compose.jsx:214 msgid "Add poll" msgstr "" -#: src/components/compose.jsx:214 +#: src/components/compose.jsx:215 msgid "Schedule post" msgstr "Schedule post" -#: src/components/compose.jsx:410 +#: src/components/compose.jsx:411 msgid "You have unsaved changes. Discard this post?" msgstr "You have unsaved changes. Discard this post?" #. placeholder {0}: unsupportedFiles.length #. placeholder {1}: unsupportedFiles[0].name #. placeholder {2}: lf.format( unsupportedFiles.map((f) => f.name), ) -#: src/components/compose.jsx:639 +#: src/components/compose.jsx:640 msgid "{0, plural, one {File {1} is not supported.} other {Files {2} are not supported.}}" msgstr "{0, plural, one {File {1} is not supported.} other {Files {2} are not supported.}}" -#: src/components/compose.jsx:649 -#: src/components/compose.jsx:667 -#: src/components/compose.jsx:1746 -#: src/components/compose.jsx:1832 +#: src/components/compose.jsx:650 +#: src/components/compose.jsx:668 +#: src/components/compose.jsx:1747 +#: src/components/compose.jsx:1833 msgid "{maxMediaAttachments, plural, one {You can only attach up to 1 file.} other {You can only attach up to # files.}}" msgstr "" -#: src/components/compose.jsx:857 +#: src/components/compose.jsx:858 msgid "Pop out" msgstr "Pop out" -#: src/components/compose.jsx:864 +#: src/components/compose.jsx:865 msgid "Minimize" msgstr "Minimize" -#: src/components/compose.jsx:900 +#: src/components/compose.jsx:901 msgid "Looks like you closed the parent window." msgstr "Looks like you closed the parent window." -#: src/components/compose.jsx:907 +#: src/components/compose.jsx:908 msgid "Looks like you already have a compose field open in the parent window and currently publishing. Please wait for it to be done and try again later." msgstr "Looks like you already have a compose field open in the parent window and currently publishing. Please wait for it to be done and try again later." -#: src/components/compose.jsx:912 +#: src/components/compose.jsx:913 msgid "Looks like you already have a compose field open in the parent window. Popping in this window will discard the changes you made in the parent window. Continue?" msgstr "Looks like you already have a compose field open in the parent window. Popping in this window will discard the changes you made in the parent window. Continue?" -#: src/components/compose.jsx:955 +#: src/components/compose.jsx:956 msgid "Pop in" msgstr "Pop in" #. placeholder {0}: replyToStatus.account.acct || replyToStatus.account.username #. placeholder {1}: rtf.format(-replyToStatusMonthsAgo, 'month') -#: src/components/compose.jsx:965 +#: src/components/compose.jsx:966 msgid "Replying to @{0}’s post (<0>{1})" msgstr "" #. placeholder {0}: replyToStatus.account.acct || replyToStatus.account.username -#: src/components/compose.jsx:975 +#: src/components/compose.jsx:976 msgid "Replying to @{0}’s post" msgstr "" -#: src/components/compose.jsx:988 +#: src/components/compose.jsx:989 msgid "Editing source post" msgstr "" -#: src/components/compose.jsx:1041 +#: src/components/compose.jsx:1042 msgid "Poll must have at least 2 options" msgstr "Poll must have at least 2 options" -#: src/components/compose.jsx:1045 +#: src/components/compose.jsx:1046 msgid "Some poll choices are empty" msgstr "Some poll choices are empty" -#: src/components/compose.jsx:1058 +#: src/components/compose.jsx:1059 msgid "Some media have no descriptions. Continue?" msgstr "Some media have no descriptions. Continue?" -#: src/components/compose.jsx:1110 +#: src/components/compose.jsx:1111 msgid "Attachment #{i} failed" msgstr "Attachment #{i} failed" -#: src/components/compose.jsx:1206 -#: src/components/status.jsx:2076 +#: src/components/compose.jsx:1207 +#: src/components/status.jsx:2098 #: src/components/timeline.jsx:989 msgid "Content warning" msgstr "" -#: src/components/compose.jsx:1222 +#: src/components/compose.jsx:1223 msgid "Content warning or sensitive media" msgstr "Content warning or sensitive media" -#: src/components/compose.jsx:1258 -#: src/components/status.jsx:93 +#: src/components/compose.jsx:1259 +#: src/components/status.jsx:94 #: src/pages/settings.jsx:306 msgid "Public" msgstr "" -#: src/components/compose.jsx:1263 +#: src/components/compose.jsx:1264 #: src/components/nav-menu.jsx:344 #: src/components/shortcuts-settings.jsx:165 -#: src/components/status.jsx:94 +#: src/components/status.jsx:95 msgid "Local" msgstr "" -#: src/components/compose.jsx:1267 -#: src/components/status.jsx:95 +#: src/components/compose.jsx:1268 +#: src/components/status.jsx:96 #: src/pages/settings.jsx:309 msgid "Unlisted" msgstr "" -#: src/components/compose.jsx:1270 -#: src/components/status.jsx:96 +#: src/components/compose.jsx:1271 +#: src/components/status.jsx:97 #: src/pages/settings.jsx:312 msgid "Followers only" msgstr "" -#: src/components/compose.jsx:1273 -#: src/components/status.jsx:97 -#: src/components/status.jsx:1954 +#: src/components/compose.jsx:1274 +#: src/components/status.jsx:98 +#: src/components/status.jsx:1976 msgid "Private mention" msgstr "" -#: src/components/compose.jsx:1282 +#: src/components/compose.jsx:1283 msgid "Post your reply" msgstr "Post your reply" -#: src/components/compose.jsx:1284 +#: src/components/compose.jsx:1285 msgid "Edit your post" msgstr "Edit your post" -#: src/components/compose.jsx:1285 +#: src/components/compose.jsx:1286 msgid "What are you doing?" msgstr "What are you doing?" -#: src/components/compose.jsx:1363 +#: src/components/compose.jsx:1364 msgid "Mark media as sensitive" msgstr "" -#: src/components/compose.jsx:1400 +#: src/components/compose.jsx:1401 msgid "Posting on <0/>" msgstr "Posting on <0/>" -#: src/components/compose.jsx:1431 -#: src/components/compose.jsx:3184 +#: src/components/compose.jsx:1432 +#: src/components/compose.jsx:3191 #: src/components/shortcuts-settings.jsx:715 #: src/pages/list.jsx:362 msgid "Add" msgstr "" -#: src/components/compose.jsx:1625 +#: src/components/compose.jsx:1626 msgid "Schedule" msgstr "Schedule" -#: src/components/compose.jsx:1627 +#: src/components/compose.jsx:1628 #: src/components/keyboard-shortcuts-help.jsx:154 -#: src/components/status.jsx:962 -#: src/components/status.jsx:1728 -#: src/components/status.jsx:1729 -#: src/components/status.jsx:2399 +#: src/components/status.jsx:984 +#: src/components/status.jsx:1750 +#: src/components/status.jsx:1751 +#: src/components/status.jsx:2421 msgid "Reply" msgstr "" -#: src/components/compose.jsx:1629 +#: src/components/compose.jsx:1630 msgid "Update" msgstr "Update" -#: src/components/compose.jsx:1630 +#: src/components/compose.jsx:1631 msgctxt "Submit button in composer" msgid "Post" msgstr "Post" -#: src/components/compose.jsx:1758 +#: src/components/compose.jsx:1759 msgid "Downloading GIF…" msgstr "Downloading GIF…" -#: src/components/compose.jsx:1786 +#: src/components/compose.jsx:1787 msgid "Failed to download GIF" msgstr "Failed to download GIF" -#: src/components/compose.jsx:1956 -#: src/components/compose.jsx:2033 +#: src/components/compose.jsx:1963 +#: src/components/compose.jsx:2040 #: src/components/nav-menu.jsx:239 msgid "More…" msgstr "" -#: src/components/compose.jsx:2465 +#: src/components/compose.jsx:2472 msgid "Uploaded" msgstr "" -#: src/components/compose.jsx:2478 +#: src/components/compose.jsx:2485 msgid "Image description" msgstr "Image description" -#: src/components/compose.jsx:2479 +#: src/components/compose.jsx:2486 msgid "Video description" msgstr "Video description" -#: src/components/compose.jsx:2480 +#: src/components/compose.jsx:2487 msgid "Audio description" msgstr "Audio description" #. placeholder {0}: prettyBytes( imageSize, ) #. placeholder {1}: prettyBytes(imageSizeLimit) -#: src/components/compose.jsx:2516 +#: src/components/compose.jsx:2523 msgid "File size too large. Uploading might encounter issues. Try reduce the file size from {0} to {1} or lower." msgstr "File size too large. Uploading might encounter issues. Try reduce the file size from {0} to {1} or lower." @@ -793,13 +793,13 @@ msgstr "File size too large. Uploading might encounter issues. Try reduce the fi #. placeholder {3}: i18n.number(height) #. placeholder {4}: i18n.number(newWidth) #. placeholder {5}: i18n.number( newHeight, ) -#: src/components/compose.jsx:2528 +#: src/components/compose.jsx:2535 msgid "Dimension too large. Uploading might encounter issues. Try reduce dimension from {2}×{3}px to {4}×{5}px." msgstr "Dimension too large. Uploading might encounter issues. Try reduce dimension from {2}×{3}px to {4}×{5}px." #. placeholder {6}: prettyBytes( videoSize, ) #. placeholder {7}: prettyBytes(videoSizeLimit) -#: src/components/compose.jsx:2536 +#: src/components/compose.jsx:2543 msgid "File size too large. Uploading might encounter issues. Try reduce the file size from {6} to {7} or lower." msgstr "File size too large. Uploading might encounter issues. Try reduce the file size from {6} to {7} or lower." @@ -807,149 +807,149 @@ msgstr "File size too large. Uploading might encounter issues. Try reduce the fi #. placeholder {9}: i18n.number(height) #. placeholder {10}: i18n.number(newWidth) #. placeholder {11}: i18n.number( newHeight, ) -#: src/components/compose.jsx:2548 +#: src/components/compose.jsx:2555 msgid "Dimension too large. Uploading might encounter issues. Try reduce dimension from {8}×{9}px to {10}×{11}px." msgstr "Dimension too large. Uploading might encounter issues. Try reduce dimension from {8}×{9}px to {10}×{11}px." -#: src/components/compose.jsx:2556 +#: src/components/compose.jsx:2563 msgid "Frame rate too high. Uploading might encounter issues." msgstr "Frame rate too high. Uploading might encounter issues." -#: src/components/compose.jsx:2616 -#: src/components/compose.jsx:2866 +#: src/components/compose.jsx:2623 +#: src/components/compose.jsx:2873 #: src/components/shortcuts-settings.jsx:726 #: src/pages/catchup.jsx:1074 #: src/pages/filters.jsx:412 msgid "Remove" msgstr "" -#: src/components/compose.jsx:2633 +#: src/components/compose.jsx:2640 #: src/compose.jsx:84 msgid "Error" msgstr "" -#: src/components/compose.jsx:2658 +#: src/components/compose.jsx:2665 msgid "Edit image description" msgstr "Edit image description" -#: src/components/compose.jsx:2659 +#: src/components/compose.jsx:2666 msgid "Edit video description" msgstr "Edit video description" -#: src/components/compose.jsx:2660 +#: src/components/compose.jsx:2667 msgid "Edit audio description" msgstr "Edit audio description" -#: src/components/compose.jsx:2705 -#: src/components/compose.jsx:2754 +#: src/components/compose.jsx:2712 +#: src/components/compose.jsx:2761 msgid "Generating description. Please wait…" msgstr "Generating description. Please wait…" #. placeholder {12}: e.message -#: src/components/compose.jsx:2725 +#: src/components/compose.jsx:2732 msgid "Failed to generate description: {12}" msgstr "Failed to generate description: {12}" -#: src/components/compose.jsx:2726 +#: src/components/compose.jsx:2733 msgid "Failed to generate description" msgstr "Failed to generate description" -#: src/components/compose.jsx:2738 -#: src/components/compose.jsx:2744 -#: src/components/compose.jsx:2790 +#: src/components/compose.jsx:2745 +#: src/components/compose.jsx:2751 +#: src/components/compose.jsx:2797 msgid "Generate description…" msgstr "" #. placeholder {13}: e?.message ? `: ${e.message}` : '' -#: src/components/compose.jsx:2777 +#: src/components/compose.jsx:2784 msgid "Failed to generate description{13}" msgstr "Failed to generate description{13}" #. placeholder {0}: localeCode2Text(lang) -#: src/components/compose.jsx:2792 +#: src/components/compose.jsx:2799 msgid "({0}) <0>— experimental" msgstr "" -#: src/components/compose.jsx:2811 +#: src/components/compose.jsx:2818 msgid "Done" msgstr "" #. placeholder {0}: i + 1 -#: src/components/compose.jsx:2847 +#: src/components/compose.jsx:2854 msgid "Choice {0}" msgstr "Choice {0}" -#: src/components/compose.jsx:2894 +#: src/components/compose.jsx:2901 msgid "Multiple choices" msgstr "" -#: src/components/compose.jsx:2897 +#: src/components/compose.jsx:2904 msgid "Duration" msgstr "" -#: src/components/compose.jsx:2928 +#: src/components/compose.jsx:2935 msgid "Remove poll" msgstr "" -#: src/components/compose.jsx:3143 +#: src/components/compose.jsx:3150 msgid "Search accounts" msgstr "Search accounts" -#: src/components/compose.jsx:3197 +#: src/components/compose.jsx:3204 #: src/components/generic-accounts.jsx:228 msgid "Error loading accounts" msgstr "" -#: src/components/compose.jsx:3341 +#: src/components/compose.jsx:3348 msgid "Custom emojis" msgstr "" -#: src/components/compose.jsx:3361 +#: src/components/compose.jsx:3368 msgid "Search emoji" msgstr "Search emoji" -#: src/components/compose.jsx:3392 +#: src/components/compose.jsx:3399 msgid "Error loading custom emojis" msgstr "" -#: src/components/compose.jsx:3403 +#: src/components/compose.jsx:3410 msgid "Recently used" msgstr "Recently used" -#: src/components/compose.jsx:3404 +#: src/components/compose.jsx:3411 msgid "Others" msgstr "Others" #. placeholder {0}: i18n.number(emojis.length - max) -#: src/components/compose.jsx:3442 +#: src/components/compose.jsx:3449 msgid "{0} more…" msgstr "" -#: src/components/compose.jsx:3580 +#: src/components/compose.jsx:3587 msgid "Search GIFs" msgstr "Search GIFs" -#: src/components/compose.jsx:3595 +#: src/components/compose.jsx:3602 msgid "Powered by GIPHY" msgstr "Powered by GIPHY" -#: src/components/compose.jsx:3603 +#: src/components/compose.jsx:3610 msgid "Type to search GIFs" msgstr "" -#: src/components/compose.jsx:3701 +#: src/components/compose.jsx:3708 #: src/components/media-modal.jsx:464 #: src/components/timeline.jsx:893 msgid "Previous" msgstr "" -#: src/components/compose.jsx:3719 +#: src/components/compose.jsx:3726 #: src/components/media-modal.jsx:483 #: src/components/timeline.jsx:910 msgid "Next" msgstr "" -#: src/components/compose.jsx:3736 +#: src/components/compose.jsx:3743 msgid "Error loading GIFs" msgstr "" @@ -972,7 +972,7 @@ msgstr "" #: src/components/drafts.jsx:128 #: src/components/list-add-edit.jsx:186 -#: src/components/status.jsx:1363 +#: src/components/status.jsx:1385 #: src/pages/filters.jsx:587 #: src/pages/scheduled-posts.jsx:367 msgid "Delete…" @@ -1181,10 +1181,10 @@ msgid "<0>l or <1>f" msgstr "" #: src/components/keyboard-shortcuts-help.jsx:175 -#: src/components/status.jsx:970 -#: src/components/status.jsx:2426 -#: src/components/status.jsx:2449 -#: src/components/status.jsx:2450 +#: src/components/status.jsx:992 +#: src/components/status.jsx:2448 +#: src/components/status.jsx:2471 +#: src/components/status.jsx:2472 msgid "Boost" msgstr "" @@ -1193,9 +1193,9 @@ msgid "<0>Shift + <1>b" msgstr "" #: src/components/keyboard-shortcuts-help.jsx:183 -#: src/components/status.jsx:1033 -#: src/components/status.jsx:2474 -#: src/components/status.jsx:2475 +#: src/components/status.jsx:1055 +#: src/components/status.jsx:2496 +#: src/components/status.jsx:2497 msgid "Bookmark" msgstr "" @@ -1254,15 +1254,15 @@ msgid "Media description" msgstr "" #: src/components/media-alt-modal.jsx:58 -#: src/components/status.jsx:1077 -#: src/components/status.jsx:1104 -#: src/components/translation-block.jsx:196 +#: src/components/status.jsx:1099 +#: src/components/status.jsx:1126 +#: src/components/translation-block.jsx:215 msgid "Translate" msgstr "" #: src/components/media-alt-modal.jsx:69 -#: src/components/status.jsx:1091 -#: src/components/status.jsx:1118 +#: src/components/status.jsx:1113 +#: src/components/status.jsx:1140 msgid "Speak" msgstr "" @@ -1299,9 +1299,9 @@ msgid "Filtered: {filterTitleStr}" msgstr "" #: src/components/media-post.jsx:134 -#: src/components/status.jsx:3568 -#: src/components/status.jsx:3664 -#: src/components/status.jsx:3742 +#: src/components/status.jsx:3590 +#: src/components/status.jsx:3686 +#: src/components/status.jsx:3764 #: src/components/timeline.jsx:978 #: src/pages/catchup.jsx:75 #: src/pages/catchup.jsx:1877 @@ -1619,8 +1619,8 @@ msgid "[Unknown notification type: {type}]" msgstr "" #: src/components/notification.jsx:441 -#: src/components/status.jsx:1047 -#: src/components/status.jsx:1057 +#: src/components/status.jsx:1069 +#: src/components/status.jsx:1079 msgid "Boosted/Liked by…" msgstr "" @@ -1646,7 +1646,7 @@ msgid "View #Wrapstodon" msgstr "View #Wrapstodon" #: src/components/notification.jsx:770 -#: src/components/status.jsx:275 +#: src/components/status.jsx:297 msgid "Read more →" msgstr "" @@ -1948,7 +1948,7 @@ msgid "Move down" msgstr "" #: src/components/shortcuts-settings.jsx:379 -#: src/components/status.jsx:1325 +#: src/components/status.jsx:1347 #: src/pages/list.jsx:171 msgid "Edit" msgstr "" @@ -2147,314 +2147,314 @@ msgstr "" msgid "Import/export settings from/to instance server (Very experimental)" msgstr "" -#: src/components/status.jsx:549 +#: src/components/status.jsx:571 msgid "<0/> <1>boosted" msgstr "" -#: src/components/status.jsx:648 +#: src/components/status.jsx:670 msgid "Sorry, your current logged-in instance can't interact with this post from another instance." msgstr "" #. placeholder {0}: username || acct -#: src/components/status.jsx:801 +#: src/components/status.jsx:823 msgid "Unliked @{0}'s post" msgstr "" #. placeholder {1}: username || acct -#: src/components/status.jsx:802 +#: src/components/status.jsx:824 msgid "Liked @{1}'s post" msgstr "Liked @{1}'s post" #. placeholder {2}: username || acct -#: src/components/status.jsx:841 +#: src/components/status.jsx:863 msgid "Unbookmarked @{2}'s post" msgstr "Unbookmarked @{2}'s post" #. placeholder {3}: username || acct -#: src/components/status.jsx:842 +#: src/components/status.jsx:864 msgid "Bookmarked @{3}'s post" msgstr "Bookmarked @{3}'s post" -#: src/components/status.jsx:939 +#: src/components/status.jsx:961 msgid "Some media have no descriptions." msgstr "" #. placeholder {0}: rtf.format(-statusMonthsAgo, 'month') -#: src/components/status.jsx:946 +#: src/components/status.jsx:968 msgid "Old post (<0>{0})" msgstr "" -#: src/components/status.jsx:970 -#: src/components/status.jsx:1010 -#: src/components/status.jsx:2426 -#: src/components/status.jsx:2449 +#: src/components/status.jsx:992 +#: src/components/status.jsx:1032 +#: src/components/status.jsx:2448 +#: src/components/status.jsx:2471 msgid "Unboost" msgstr "" -#: src/components/status.jsx:986 -#: src/components/status.jsx:2441 +#: src/components/status.jsx:1008 +#: src/components/status.jsx:2463 msgid "Quote" msgstr "" #. placeholder {4}: username || acct -#: src/components/status.jsx:998 +#: src/components/status.jsx:1020 msgid "Unboosted @{4}'s post" msgstr "Unboosted @{4}'s post" #. placeholder {5}: username || acct -#: src/components/status.jsx:999 +#: src/components/status.jsx:1021 msgid "Boosted @{5}'s post" msgstr "Boosted @{5}'s post" -#: src/components/status.jsx:1011 +#: src/components/status.jsx:1033 msgid "Boost…" msgstr "" -#: src/components/status.jsx:1023 -#: src/components/status.jsx:1738 -#: src/components/status.jsx:2462 +#: src/components/status.jsx:1045 +#: src/components/status.jsx:1760 +#: src/components/status.jsx:2484 msgid "Unlike" msgstr "" -#: src/components/status.jsx:1024 -#: src/components/status.jsx:1738 -#: src/components/status.jsx:1739 -#: src/components/status.jsx:2462 -#: src/components/status.jsx:2463 +#: src/components/status.jsx:1046 +#: src/components/status.jsx:1760 +#: src/components/status.jsx:1761 +#: src/components/status.jsx:2484 +#: src/components/status.jsx:2485 msgid "Like" msgstr "" -#: src/components/status.jsx:1033 -#: src/components/status.jsx:2474 +#: src/components/status.jsx:1055 +#: src/components/status.jsx:2496 msgid "Unbookmark" msgstr "" #. placeholder {0}: username || acct -#: src/components/status.jsx:1141 +#: src/components/status.jsx:1163 msgid "View post by <0>@{0}" msgstr "" -#: src/components/status.jsx:1162 +#: src/components/status.jsx:1184 msgid "Show Edit History" msgstr "" -#: src/components/status.jsx:1165 +#: src/components/status.jsx:1187 msgid "Edited: {editedDateText}" msgstr "" -#: src/components/status.jsx:1232 -#: src/components/status.jsx:3243 +#: src/components/status.jsx:1254 +#: src/components/status.jsx:3265 msgid "Embed post" msgstr "" -#: src/components/status.jsx:1246 +#: src/components/status.jsx:1268 msgid "Conversation unmuted" msgstr "" -#: src/components/status.jsx:1246 +#: src/components/status.jsx:1268 msgid "Conversation muted" msgstr "" -#: src/components/status.jsx:1252 +#: src/components/status.jsx:1274 msgid "Unable to unmute conversation" msgstr "" -#: src/components/status.jsx:1253 +#: src/components/status.jsx:1275 msgid "Unable to mute conversation" msgstr "" -#: src/components/status.jsx:1262 +#: src/components/status.jsx:1284 msgid "Unmute conversation" msgstr "" -#: src/components/status.jsx:1269 +#: src/components/status.jsx:1291 msgid "Mute conversation" msgstr "" -#: src/components/status.jsx:1285 +#: src/components/status.jsx:1307 msgid "Post unpinned from profile" msgstr "" -#: src/components/status.jsx:1286 +#: src/components/status.jsx:1308 msgid "Post pinned to profile" msgstr "" -#: src/components/status.jsx:1291 +#: src/components/status.jsx:1313 msgid "Unable to unpin post" msgstr "" -#: src/components/status.jsx:1291 +#: src/components/status.jsx:1313 msgid "Unable to pin post" msgstr "" -#: src/components/status.jsx:1300 +#: src/components/status.jsx:1322 msgid "Unpin from profile" msgstr "" -#: src/components/status.jsx:1307 +#: src/components/status.jsx:1329 msgid "Pin to profile" msgstr "" -#: src/components/status.jsx:1336 +#: src/components/status.jsx:1358 msgid "Delete this post?" msgstr "" -#: src/components/status.jsx:1352 +#: src/components/status.jsx:1374 msgid "Post deleted" msgstr "" -#: src/components/status.jsx:1355 +#: src/components/status.jsx:1377 msgid "Unable to delete post" msgstr "" -#: src/components/status.jsx:1383 +#: src/components/status.jsx:1405 msgid "Report post…" msgstr "" #. placeholder {6}: username || acct -#: src/components/status.jsx:1453 +#: src/components/status.jsx:1475 msgid "Unboosted @{6}'s post" msgstr "Unboosted @{6}'s post" #. placeholder {7}: username || acct -#: src/components/status.jsx:1454 +#: src/components/status.jsx:1476 msgid "Boosted @{7}'s post" msgstr "Boosted @{7}'s post" -#: src/components/status.jsx:1739 -#: src/components/status.jsx:1775 -#: src/components/status.jsx:2463 +#: src/components/status.jsx:1761 +#: src/components/status.jsx:1797 +#: src/components/status.jsx:2485 msgid "Liked" msgstr "" -#: src/components/status.jsx:1772 -#: src/components/status.jsx:2450 +#: src/components/status.jsx:1794 +#: src/components/status.jsx:2472 msgid "Boosted" msgstr "" -#: src/components/status.jsx:1782 -#: src/components/status.jsx:2475 +#: src/components/status.jsx:1804 +#: src/components/status.jsx:2497 msgid "Bookmarked" msgstr "" -#: src/components/status.jsx:1786 +#: src/components/status.jsx:1808 msgid "Pinned" msgstr "" -#: src/components/status.jsx:1832 -#: src/components/status.jsx:2289 +#: src/components/status.jsx:1854 +#: src/components/status.jsx:2311 msgid "Deleted" msgstr "" -#: src/components/status.jsx:1873 +#: src/components/status.jsx:1895 msgid "{repliesCount, plural, one {# reply} other {# replies}}" msgstr "" #. placeholder {0}: snapStates.statusThreadNumber[sKey] ? ` ${snapStates.statusThreadNumber[sKey]}/X` : '' -#: src/components/status.jsx:1963 +#: src/components/status.jsx:1985 msgid "Thread{0}" msgstr "" -#: src/components/status.jsx:2039 -#: src/components/status.jsx:2101 -#: src/components/status.jsx:2186 +#: src/components/status.jsx:2061 +#: src/components/status.jsx:2123 +#: src/components/status.jsx:2208 msgid "Show less" msgstr "" -#: src/components/status.jsx:2039 -#: src/components/status.jsx:2101 +#: src/components/status.jsx:2061 +#: src/components/status.jsx:2123 msgid "Show content" msgstr "" -#: src/components/status.jsx:2186 +#: src/components/status.jsx:2208 msgid "Show media" msgstr "" -#: src/components/status.jsx:2323 +#: src/components/status.jsx:2345 msgid "Edited" msgstr "" -#: src/components/status.jsx:2400 +#: src/components/status.jsx:2422 msgid "Comments" msgstr "" #. More from [Author] -#: src/components/status.jsx:2701 +#: src/components/status.jsx:2723 msgid "More from <0/>" msgstr "More from <0/>" -#: src/components/status.jsx:3003 +#: src/components/status.jsx:3025 msgid "Edit History" msgstr "" -#: src/components/status.jsx:3007 +#: src/components/status.jsx:3029 msgid "Failed to load history" msgstr "" -#: src/components/status.jsx:3012 +#: src/components/status.jsx:3034 #: src/pages/annual-report.jsx:45 msgid "Loading…" msgstr "" -#: src/components/status.jsx:3248 +#: src/components/status.jsx:3270 msgid "HTML Code" msgstr "" -#: src/components/status.jsx:3265 +#: src/components/status.jsx:3287 msgid "HTML code copied" msgstr "" -#: src/components/status.jsx:3268 +#: src/components/status.jsx:3290 msgid "Unable to copy HTML code" msgstr "" -#: src/components/status.jsx:3280 +#: src/components/status.jsx:3302 msgid "Media attachments:" msgstr "" -#: src/components/status.jsx:3302 +#: src/components/status.jsx:3324 msgid "Account Emojis:" msgstr "" -#: src/components/status.jsx:3333 -#: src/components/status.jsx:3378 +#: src/components/status.jsx:3355 +#: src/components/status.jsx:3400 msgid "static URL" msgstr "" -#: src/components/status.jsx:3347 +#: src/components/status.jsx:3369 msgid "Emojis:" msgstr "" -#: src/components/status.jsx:3392 +#: src/components/status.jsx:3414 msgid "Notes:" msgstr "" -#: src/components/status.jsx:3396 +#: src/components/status.jsx:3418 msgid "This is static, unstyled and scriptless. You may need to apply your own styles and edit as needed." msgstr "" -#: src/components/status.jsx:3402 +#: src/components/status.jsx:3424 msgid "Polls are not interactive, becomes a list with vote counts." msgstr "" -#: src/components/status.jsx:3407 +#: src/components/status.jsx:3429 msgid "Media attachments can be images, videos, audios or any file types." msgstr "" -#: src/components/status.jsx:3413 +#: src/components/status.jsx:3435 msgid "Post could be edited or deleted later." msgstr "" -#: src/components/status.jsx:3419 +#: src/components/status.jsx:3441 msgid "Preview" msgstr "" -#: src/components/status.jsx:3428 +#: src/components/status.jsx:3450 msgid "Note: This preview is lightly styled." msgstr "" #. [Name] [Visibility icon] boosted -#: src/components/status.jsx:3672 +#: src/components/status.jsx:3694 msgid "<0/> <1/> boosted" msgstr "" @@ -2491,28 +2491,28 @@ msgstr "" msgid "<0>Filtered: <1>{0}" msgstr "" -#: src/components/translation-block.jsx:153 +#: src/components/translation-block.jsx:172 msgid "Auto-translated from {sourceLangText}" msgstr "" -#: src/components/translation-block.jsx:191 +#: src/components/translation-block.jsx:210 msgid "Translating…" msgstr "" -#: src/components/translation-block.jsx:194 +#: src/components/translation-block.jsx:213 msgid "Translate from {sourceLangText} (auto-detected)" msgstr "" -#: src/components/translation-block.jsx:195 +#: src/components/translation-block.jsx:214 msgid "Translate from {sourceLangText}" msgstr "" #. placeholder {0}: detectedLang ?? '…' -#: src/components/translation-block.jsx:223 +#: src/components/translation-block.jsx:242 msgid "Auto ({0})" msgstr "" -#: src/components/translation-block.jsx:236 +#: src/components/translation-block.jsx:255 msgid "Failed to translate" msgstr "" diff --git a/src/utils/browser-translator.js b/src/utils/browser-translator.js new file mode 100644 index 00000000..6e7caf17 --- /dev/null +++ b/src/utils/browser-translator.js @@ -0,0 +1,107 @@ +export const supportsBrowserTranslator = + 'ai' in self && 'translator' in self.ai; + +// https://developer.chrome.com/docs/ai/language-detection +export let langDetector; +if (supportsBrowserTranslator) { + try { + const languageDetectorCapabilities = + await self.ai.languageDetector.capabilities(); + const canDetect = languageDetectorCapabilities.capabilities; + if (canDetect === 'no') { + // The language detector isn't usable. + // return; + } + if (canDetect === 'readily') { + // The language detector can immediately be used. + langDetector = await self.ai.languageDetector.create(); + } else { + // The language detector can be used after model download. + langDetector = await self.ai.languageDetector.create({ + monitor(m) { + m.addEventListener('downloadprogress', (e) => { + console.log( + `Detector: Downloaded ${e.loaded} of ${e.total} bytes.`, + ); + }); + }, + }); + await langDetector.ready; + } + } catch (e) { + console.error(e); + } +} + +// https://developer.chrome.com/docs/ai/translator-api +export const translate = async (text, source, target) => { + let detectedSourceLanguage; + const originalSource = source; + if (source === 'auto') { + try { + const results = await langDetector.detect(text); + source = results[0].detectedLanguage; + detectedSourceLanguage = source; + } catch (e) { + console.error(e); + return { + error: e, + }; + } + } + console.groupCollapsed( + '💬 BROWSER TRANSLATE', + originalSource, + detectedSourceLanguage, + target, + ); + console.log(text); + try { + const translatorCapabilities = await self.ai.translator.capabilities(); + const canTranslate = translatorCapabilities.languagePairAvailable( + source, + target, + ); + if (canTranslate === 'no') { + console.groupEnd(); + return { + error: `Unsupported language pair: ${source} -> ${target}`, + }; + } + let translator; + if (canTranslate === 'readily') { + translator = await self.ai.translator.create({ + sourceLanguage: source, + targetLanguage: target, + }); + } else { + translator = await self.ai.translator.create({ + sourceLanguage: source, + targetLanguage: target, + monitor(m) { + m.addEventListener('downloadprogress', (e) => { + console.log( + `Translate ${source} -> ${target}: Downloaded ${e.loaded} of ${e.total} bytes.`, + ); + }); + }, + }); + } + + const content = await translator.translate(text); + console.log(content); + console.groupEnd(); + + return { + content, + detectedSourceLanguage, + provider: 'browser', + }; + } catch (e) { + console.groupEnd(); + console.error(e); + return { + error: e, + }; + } +};