Browse Source

feat: implement userutils 7.3.0 translation features

Sv443 7 months ago
parent
commit
b4de125dd7
8 changed files with 89 additions and 19 deletions
  1. 2 0
      changelog.md
  2. 43 0
      contributing.md
  3. 1 1
      package.json
  4. 5 5
      pnpm-lock.yaml
  5. 3 1
      src/interface.ts
  6. 10 3
      src/menu/menu_old.ts
  7. 6 2
      src/types.ts
  8. 19 7
      src/utils/translations.ts

+ 2 - 0
changelog.md

@@ -79,6 +79,8 @@
     - `getDomain()` returns the current domain ("yt" or "ytm")
     - `waitVideoElementReady()` returns a promise that resolves when the video element is ready
     - `getCurrentMediaType()` (on YTM only) returns the current media type ("video" or "song")
+    - `tl()` returns the translation for the provided translation key and provided locale
+    - `tlp()` returns the translation for the provided translation key, including pluralization identifier and provided locale
   - SelectorObserver / `addSelectorListener()` changes:
     - Added `ytMasthead` instance for the title bar on YT
     - Renamed all YT-specific instances to have the `yt` prefix

+ 43 - 0
contributing.md

@@ -404,6 +404,8 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
   - [hasKeyFor()](#haskeyfor) - Checks if the specified translation key exists in the specified locale
   - [t()](#t) - Translates the specified translation key using the currently set locale
   - [tp()](#tp) - Translates the specified translation key including pluralization using the currently set locale
+  - [tl()](#tl) - Returns the translation for the provided key and provided locale
+  - [tlp()](#tlp) - Returns the translation for the provided locale and key, including pluralization identifier
 - Feature config:
   - [getFeatures()](#getfeatures) 🔒 - Returns the current BYTM feature configuration object
   - [saveFeatures()](#savefeatures) 🔒 - Overwrites the current BYTM feature configuration object with the provided one
@@ -1063,6 +1065,47 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
 
 <br>
 
+> #### tl()
+> Usage:  
+> ```ts
+> unsafeWindow.BYTM.tl(locale: string, key: TFuncKey, ...values: Stringifiable[]): string
+> ```  
+>   
+> Description:  
+> Returns the translation for the provided translation key and locale.  
+> Useful to get the translation for a specific locale without changing the currently set locale.  
+> To see a list of possible translation values, check the file [`assets/translations/en_US.json`](assets/translations/en_US.json)  
+>   
+> Arguments:  
+> - `locale` - The locale to get the translation for.
+> - `translationKey` - The key of the translation to get.
+> - `...values` - A spread parameter of values that can be converted to strings to replace the numbered placeholders in the translation with.
+> 
+> For an example, see [`t()`](#t) which behaves in the same way, but uses the currently set locale instead of a specified one.
+
+<br>
+
+> #### tlp()
+> Usage:  
+> ```ts
+> unsafeWindow.BYTM.tlp(locale: string, key: TFuncKey, num: number | unknown[] | NodeList, ...values: Stringifiable[]): string
+> ```  
+>   
+> Description:  
+> Returns the translation for the provided translation key, including pluralization identifier and locale.  
+> Useful to get the translation for a specific locale without changing the currently set locale.  
+> To see a list of possible translation values, check the file [`assets/translations/en_US.json`](assets/translations/en_US.json)  
+>   
+> Arguments:  
+> - `locale` - The locale to get the translation for.
+> - `key` - The key of the translation to get.
+> - `num` - The number of items to determine the pluralization identifier from. Can also be an array or NodeList.
+> - `...values` - A spread parameter of values that can be converted to strings to replace the numbered placeholders in the translation with.
+>   
+> For an example, see [`tp()`](#tp) which behaves in the same way, but uses the currently set locale instead of a specified one.
+
+<br>
+
 > #### getFeatures()
 > Usage:  
 > ```ts

+ 1 - 1
package.json

@@ -64,7 +64,7 @@
     "openuserjs": "https://openuserjs.org/scripts/Sv443/BetterYTM"
   },
   "dependencies": {
-    "@sv443-network/userutils": "^7.2.2",
+    "@sv443-network/userutils": "^7.3.0",
     "compare-versions": "^6.1.0",
     "dompurify": "^3.1.6",
     "marked": "^12.0.2",

+ 5 - 5
pnpm-lock.yaml

@@ -9,8 +9,8 @@ importers:
   .:
     dependencies:
       '@sv443-network/userutils':
-        specifier: ^7.2.2
-        version: 7.2.2
+        specifier: ^7.3.0
+        version: 7.3.0
       compare-versions:
         specifier: ^6.1.0
         version: 6.1.0
@@ -1675,8 +1675,8 @@ packages:
   '@storybook/[email protected]':
     resolution: {integrity: sha512-UJ97iqI+0Mk13I6ayd3TaBfSFBkWnEauwTnFMQe1dN/L3wTh8laOBaLa0Vr3utRSnt2b5hpcw/nq7azB/Gx4Yw==}
 
-  '@sv443-network/userutils@7.2.2':
-    resolution: {integrity: sha512-5fgJV9vuwSs8wH+B3UG0ziYjLrzAQbAhjj25T4U4Y4IC9jh7/QQ0S7lfWokVncV4+Lv0KDg26GgszziBYe4jOA==}
+  '@sv443-network/userutils@7.3.0':
+    resolution: {integrity: sha512-CCgKvlgKkY+7p7x9PYheMIKUWUzQeKNGg/kqXyj4MJsNQr6JedeUs0GYldin1W8lIwoYXqA/Ifr4V6KmGDltyw==}
 
   '@testing-library/[email protected]':
     resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==}
@@ -6651,7 +6651,7 @@ snapshots:
       '@types/express': 4.17.21
       file-system-cache: 2.3.0
 
-  '@sv443-network/userutils@7.2.2':
+  '@sv443-network/userutils@7.3.0':
     dependencies:
       nanoevents: 9.0.0
 

+ 3 - 1
src/interface.ts

@@ -1,7 +1,7 @@
 import * as UserUtils from "@sv443-network/userutils";
 import * as compareVersions from "compare-versions";
 import { mode, branch, host, buildNumber, compressionFormat, scriptInfo } from "./constants.js";
-import { getDomain, waitVideoElementReady, getResourceUrl, getSessionId, getVideoTime, log, setLocale, getLocale, hasKey, hasKeyFor, t, tp, type TrLocale, info, error, onInteraction, getThumbnailUrl, getBestThumbnailUrl, fetchVideoVotes, setInnerHtml, getCurrentMediaType } from "./utils/index.js";
+import { getDomain, waitVideoElementReady, getResourceUrl, getSessionId, getVideoTime, log, setLocale, getLocale, hasKey, hasKeyFor, t, tp, type TrLocale, info, error, onInteraction, getThumbnailUrl, getBestThumbnailUrl, fetchVideoVotes, setInnerHtml, getCurrentMediaType, tl, tlp } from "./utils/index.js";
 import { addSelectorListener } from "./observers.js";
 import { getFeatures, setFeatures } from "./config.js";
 import { autoLikeStore, featInfo, fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong } from "./features/index.js";
@@ -137,6 +137,8 @@ const globalFuncs: InterfaceFunctions = {
   hasKeyFor,
   t,
   tp,
+  tl,
+  tlp,
 
   // feature config:
   /*🔒*/ getFeatures: getFeaturesInterface,

+ 10 - 3
src/menu/menu_old.ts

@@ -2,7 +2,7 @@ import { compress, debounce, isScrollable, type Stringifiable } from "@sv443-net
 import { type defaultData, formatVersion, getFeature, getFeatures, migrations, setFeatures } from "../config.js";
 import { buildNumber, compressionFormat, host, mode, scriptInfo } from "../constants.js";
 import { featInfo, disableBeforeUnload } from "../features/index.js";
-import { error, getResourceUrl, info, log, resourceAsString, getLocale, hasKey, initTranslations, setLocale, t, arrayWithSeparators, tp, type TrKey, onInteraction, getDomain, copyToClipboard, warn, compressionSupported, tryToDecompressAndParse, setInnerHtml, type TrLocale } from "../utils/index.js";
+import { error, getResourceUrl, info, log, resourceAsString, getLocale, hasKey, initTranslations, setLocale, t, arrayWithSeparators, tp, type TrKey, onInteraction, getDomain, copyToClipboard, warn, compressionSupported, tryToDecompressAndParse, setInnerHtml, type TrLocale, tl } from "../utils/index.js";
 import { emitSiteEvent, siteEvents } from "../siteEvents.js";
 import { getChangelogDialog, getFeatHelpDialog, showPrompt } from "../dialogs/index.js";
 import type { FeatureCategory, FeatureKey, FeatureConfig, HotkeyObj, FeatureInfo } from "../types.js";
@@ -314,11 +314,18 @@ async function mountCfgMenu() {
       const newText = t("lang_changed_prompt_reload");
 
       const newLangEmoji = localeMapping[featConf.locale]?.emoji ? `${localeMapping[featConf.locale].emoji}\n` : "";
-      const initLangEmoji = initLocale && localeMapping[initLocale]?.emoji ? `${localeMapping[initLocale].emoji}\n` : "";
+      const initLangEmoji = localeMapping[initLocale!]?.emoji ? `${localeMapping[initLocale!].emoji}\n` : "";
 
       const confirmText = newText !== initLangReloadText ? `${newLangEmoji}${newText}\n\n\n${initLangEmoji}${initLangReloadText}` : newText;
 
-      if(await showPrompt({ type: "confirm", message: confirmText })) {
+      if(await showPrompt({
+        type: "confirm",
+        message: confirmText,
+        confirmBtnText: () => `${t("prompt_confirm")} / ${tl(initLocale!, "prompt_confirm")}`,
+        confirmBtnTooltip: () => `${t("click_to_confirm_tooltip")} / ${tl(initLocale!, "click_to_confirm_tooltip")}`,
+        denyBtnText: (type) => `${t(type === "alert" ? "prompt_close" : "prompt_cancel")} / ${tl(initLocale!, type === "alert" ? "prompt_close" : "prompt_cancel")}`,
+        denyBtnTooltip: (type) => `${t(type === "alert" ? "click_to_close_tooltip" : "click_to_cancel_tooltip")} / ${tl(initLocale!, type === "alert" ? "click_to_close_tooltip" : "click_to_cancel_tooltip")}`,
+      })) {
         closeCfgMenu();
         disableBeforeUnload();
         location.reload();

+ 6 - 2
src/types.ts

@@ -4,7 +4,7 @@ import type { scriptInfo } from "./constants.js";
 import type { addSelectorListener } from "./observers.js";
 import type resources from "../assets/resources.json";
 import type locales from "../assets/locales.json";
-import type { getResourceUrl, getSessionId, getVideoTime, TrLocale, t, tp, fetchVideoVotes, onInteraction, getThumbnailUrl, getBestThumbnailUrl, getLocale, hasKey, hasKeyFor, getDomain, waitVideoElementReady, setInnerHtml, getCurrentMediaType } from "./utils/index.js";
+import type { getResourceUrl, getSessionId, getVideoTime, TrLocale, t, tp, fetchVideoVotes, onInteraction, getThumbnailUrl, getBestThumbnailUrl, getLocale, hasKey, hasKeyFor, getDomain, waitVideoElementReady, setInnerHtml, getCurrentMediaType, tl, tlp } from "./utils/index.js";
 import type { SiteEventsMap } from "./siteEvents.js";
 import type { InterfaceEventsMap, getAutoLikeDataInterface, getFeaturesInterface, getPluginInfo, registerPlugin, saveAutoLikeDataInterface, saveFeaturesInterface, setLocaleInterface } from "./interface.js";
 import type { BytmDialog, ExImDialog, createCircularBtn, createHotkeyInput, createRipple, createToggleInput, showIconToast, showToast } from "./components/index.js";
@@ -315,10 +315,14 @@ export type InterfaceFunctions = {
   hasKey: typeof hasKey;
   /** Returns whether a translation key exists for the provided locale */
   hasKeyFor: typeof hasKeyFor;
-  /** Returns the translation for the provided translation key and set locale (check the files in the folder `assets/translations`) */
+  /** Returns the translation for the provided translation key and currently set locale (check the files in the folder `assets/translations`) */
   t: typeof t;
   /** Returns the translation for the provided translation key, including pluralization identifier and set locale (check the files in the folder `assets/translations`) */
   tp: typeof tp;
+  /** Returns the translation for the provided translation key and provided locale (check the files in the folder `assets/translations`) */
+  tl: typeof tl;
+  /** Returns the translation for the provided translation key, including pluralization identifier and provided locale (check the files in the folder `assets/translations`) */
+  tlp: typeof tlp;
 
   // feature config:
   /** 🔒 Returns the current feature configuration */

+ 19 - 7
src/utils/translations.ts

@@ -11,8 +11,6 @@ export type TrLocale = keyof typeof langMapping;
 export type TrKey = keyof (typeof tr_enUS["translations"]);
 type TFuncKey = TrKey | (string & {});
 
-/** Contains all translation keys of all initialized and loaded translations */
-const allTrKeys = new Map<TrLocale, Set<TrKey>>();
 /** Contains the identifiers of all initialized and loaded translation locales */
 const initializedLocales = new Set<TrLocale>();
 
@@ -41,7 +39,6 @@ export async function initTranslations(locale: TrLocale) {
     };
 
     tr.addLanguage(locale, translations);
-    allTrKeys.set(locale, new Set(Object.keys(translations) as TrKey[]));
 
     info(`Loaded translations for locale '${locale}'`);
   }
@@ -81,7 +78,7 @@ export function hasKey(key: TFuncKey) {
 
 /** Returns whether the given translation key exists in the given locale */
 export function hasKeyFor(locale: TrLocale, key: TFuncKey) {
-  return allTrKeys.get(locale)?.has(key as TrKey) ?? false;
+  return typeof tr.getTranslations(locale)?.[key] === "string";
 }
 
 /** Returns the translated string for the given key, after optionally inserting values */
@@ -90,18 +87,33 @@ export function t(key: TFuncKey, ...values: Stringifiable[]) {
 }
 
 /**
- * Returns the translated string for the given key with an added pluralization identifier based on the passed `num`  
+ * Returns the translated string for the given {@linkcode key} with an added pluralization identifier based on the passed {@linkcode num}  
+ * Also inserts the passed {@linkcode values} into the translation at the markers `%1`, `%2`, etc.  
  * Tries to fall back to the non-pluralized syntax if no translation was found
  */
 export function tp(key: TFuncKey, num: number | unknown[] | NodeList, ...values: Stringifiable[]) {
+  return tlp(getLocale(), key, num, ...values);
+}
+
+/** Returns the translated string for the given key in the specified locale, after optionally inserting values */
+export function tl(locale: TrLocale, key: TFuncKey, ...values: Stringifiable[]) {
+  return tr.forLang(locale, key, ...values);
+}
+
+/**
+ * Returns the translated string for the given {@linkcode key} in the given {@linkcode locale} with an added pluralization identifier based on the passed {@linkcode num}  
+ * Also inserts the passed {@linkcode values} into the translation at the markers `%1`, `%2`, etc.  
+ * Tries to fall back to the non-pluralized syntax if no translation was found
+ */
+export function tlp(locale: TrLocale, key: TFuncKey, num: number | unknown[] | NodeList, ...values: Stringifiable[]) {
   if(typeof num !== "number")
     num = num.length;
   const plNum = num === 1 ? "1" : "n";
 
-  const trans = t(`${key}-${plNum}`, ...values);
+  const trans = tl(locale, `${key}-${plNum}`, ...values);
 
   if(trans === key)
     return t(key, ...values);
 
   return trans;
-}
+};