@sv443-network/userutils 8.3.3 → 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @sv443-network/userutils
2
2
 
3
+ ## 8.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e10d629: Added function `digitCount()` to calculate the amount of digits in the passed number
8
+ - 949877a: Added support for nested objects in translations (e.g. `tr("foo.bar.baz")`)
9
+ - 52d392a: Added `ValueGen` and `StringGen` types with accompanying `consumeGen()` and `consumeStringGen()` functions to allow for super flexible typing and declaration of values
10
+ - bbce0e1: Added overload to `clamp()` without `min` parameter
11
+
3
12
  ## 8.3.3
4
13
 
5
14
  ### Patch Changes
package/README.md CHANGED
@@ -23,7 +23,7 @@ View the documentation of previous major releases:
23
23
  </sup>
24
24
  <sub>
25
25
 
26
- <a href="https://github.com/Sv443-Network/UserUtils/blob/v7.3.0/README.md" rel="noopener noreferrer">7.3.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v6.3.0/README.md" rel="noopener noreferrer">6.3.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v5.0.1/README.md" rel="noopener noreferrer">5.0.1</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v4.2.1/README.md" rel="noopener noreferrer">4.2.1</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v3.0.0/README.md" rel="noopener noreferrer">3.0.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v2.0.1/README.md" rel="noopener noreferrer">2.0.1</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v1.2.0/README.md" rel="noopener noreferrer">1.2.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v0.5.3/README.md" rel="noopener noreferrer">0.5.3</a>
26
+ <a href="https://github.com/Sv443-Network/UserUtils/blob/v8.0.0/README.md" rel="noopener noreferrer">8.0.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v7.0.0/README.md" rel="noopener noreferrer">7.0.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v6.0.0/README.md" rel="noopener noreferrer">6.0.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v5.0.0/README.md" rel="noopener noreferrer">5.0.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v4.0.0/README.md" rel="noopener noreferrer">4.0.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v3.0.0/README.md" rel="noopener noreferrer">3.0.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v2.0.0/README.md" rel="noopener noreferrer">2.0.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v1.0.0/README.md" rel="noopener noreferrer">1.0.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v0.5.3/README.md" rel="noopener noreferrer">0.5.3</a>
27
27
 
28
28
  </sub>
29
29
  </div>
@@ -52,6 +52,7 @@ View the documentation of previous major releases:
52
52
  - [`clamp()`](#clamp) - constrain a number between a min and max value
53
53
  - [`mapRange()`](#maprange) - map a number from one range to the same spot in another range
54
54
  - [`randRange()`](#randrange) - generate a random number between a min and max boundary
55
+ - [`digitCount()`](#digitcount) - calculate the amount of digits in a number
55
56
  - [**Misc:**](#misc)
56
57
  - [`DataStore`](#datastore) - class that manages a hybrid sync & async persistent JSON database, including data migration
57
58
  - [`DataStoreSerializer`](#datastoreserializer) - class for importing & exporting data of multiple DataStore instances, including compression, checksumming and running migrations
@@ -66,14 +67,16 @@ View the documentation of previous major releases:
66
67
  - [`decompress()`](#decompress) - decompress a previously compressed string
67
68
  - [`computeHash()`](#computehash) - compute the hash / checksum of a string or ArrayBuffer
68
69
  - [`randomId()`](#randomid) - generate a random ID of a given length and radix
70
+ - [`consumeGen()`](#consumegen) - consumes a ValueGen and returns the value
71
+ - [`consumeStringGen()`](#consumestringgen) - consumes a StringGen and returns the string
69
72
  - [**Arrays:**](#arrays)
70
73
  - [`randomItem()`](#randomitem) - returns a random item from an array
71
74
  - [`randomItemIndex()`](#randomitemindex) - returns a tuple of a random item and its index from an array
72
75
  - [`takeRandomItem()`](#takerandomitem) - returns a random item from an array and mutates it to remove the item
73
76
  - [`randomizeArray()`](#randomizearray) - returns a copy of the array with its items in a random order
74
77
  - [**Translation:**](#translation)
75
- - [`tr()`](#tr) - simple translation of a string to another language
76
- - [`tr.forLang()`](#trforlang) - specify a language besides the currently set one for the translation
78
+ - [`tr()`](#tr) - simple JSON-based translation system with placeholder and nesting support
79
+ - [`tr.forLang()`](#trforlang) - translate with the specified language instead of the currently active one
77
80
  - [`tr.addLanguage()`](#traddlanguage) - add a language and its translations
78
81
  - [`tr.setLanguage()`](#trsetlanguage) - set the currently active language for translations
79
82
  - [`tr.getLanguage()`](#trgetlanguage) - returns the currently active language
@@ -89,6 +92,8 @@ View the documentation of previous major releases:
89
92
  - [`NonEmptyString`](#nonemptystring) - any string that should have at least one character
90
93
  - [`LooseUnion`](#looseunion) - a union that gives autocomplete in the IDE but also allows any other value of the same type
91
94
  - [`Prettify`](#prettify) - expands a complex type into a more readable format while keeping functionality the same
95
+ - [`ValueGen`](#valuegen) - a "generator" value that allows for super flexible value typing and declaration
96
+ - [`StringGen`](#stringgen) - a "generator" string that allows for super flexible string typing and declaration, including enhanced support for unions
92
97
 
93
98
  <br><br>
94
99
 
@@ -952,9 +957,11 @@ setInnerHtmlUnsafe(myXssElement, userModifiableVariable);
952
957
  Usage:
953
958
  ```ts
954
959
  clamp(num: number, min: number, max: number): number
960
+ clamp(num: number, max: number): number
955
961
  ```
956
962
 
957
963
  Clamps a number between a min and max boundary (inclusive).
964
+ If only the `num` and `max` arguments are passed, the `min` boundary will be set to 0.
958
965
 
959
966
  <details><summary><b>Example - click to view</b></summary>
960
967
 
@@ -962,13 +969,14 @@ Clamps a number between a min and max boundary (inclusive).
962
969
  import { clamp } from "@sv443-network/userutils";
963
970
 
964
971
  clamp(7, 0, 10); // 7
965
- clamp(-1, 0, 10); // 0
972
+ clamp(7, 10); // 7 (equivalent to the above)
973
+ clamp(-1, 10); // 0
966
974
  clamp(5, -5, 0); // 0
967
975
  clamp(99999, 0, 10); // 10
968
976
 
969
- // clamp without a min or max boundary:
970
- clamp(-99999, -Infinity, 0); // -99999
971
- clamp(99999, 0, Infinity); // 99999
977
+ // use Infinity to clamp without a min or max boundary:
978
+ clamp(Number.MAX_SAFE_INTEGER, Infinity); // 9007199254740991
979
+ clamp(Number.MIN_SAFE_INTEGER, -Infinity, 0); // -9007199254740991
972
980
  ```
973
981
  </details>
974
982
 
@@ -1042,6 +1050,40 @@ benchmark(true); // Generated 100k in 461ms
1042
1050
  ```
1043
1051
  </details>
1044
1052
 
1053
+ <br>
1054
+
1055
+ ### digitCount()
1056
+ Usage:
1057
+ ```ts
1058
+ digitCount(num: number | Stringifiable): number
1059
+ ```
1060
+
1061
+ Calculates and returns the amount of digits in the given number.
1062
+ The given value will be converted by being passed to `String()` and then `Number()` before the calculation.
1063
+ Returns `NaN` if the number is invalid.
1064
+
1065
+ <details><summary><b>Example - click to view</b></summary>
1066
+
1067
+ ```ts
1068
+ import { digitCount } from "@sv443-network/userutils";
1069
+
1070
+ const num1 = 123;
1071
+ const num2 = 123456789;
1072
+ const num3 = " 123456789 ";
1073
+ const num4 = Number.MAX_SAFE_INTEGER;
1074
+ const num5 = "a123b456c789d";
1075
+ const num6 = parseInt("0x123456789abcdef", 16);
1076
+
1077
+ digitCount(num1); // 3
1078
+ digitCount(num2); // 9
1079
+ digitCount(num3); // 9
1080
+ digitCount(num4); // 16
1081
+ digitCount(num5); // NaN (because hex conversion has to be done through parseInt(str, 16)), like below:
1082
+ digitCount(num6); // 17
1083
+ ```
1084
+
1085
+ </details>
1086
+
1045
1087
  <br><br>
1046
1088
 
1047
1089
  <!-- #region Misc -->
@@ -1728,15 +1770,15 @@ If an array or NodeList is passed, the amount of contained items will be used.
1728
1770
  ```ts
1729
1771
  import { autoPlural } from "@sv443-network/userutils";
1730
1772
 
1731
- autoPlural("apple", 0); // "apples"
1732
- autoPlural("apple", 1); // "apple"
1733
- autoPlural("apple", 2); // "apples"
1773
+ autoPlural("item", 0); // "items"
1774
+ autoPlural("item", 1); // "item"
1775
+ autoPlural("item", 2); // "items"
1734
1776
 
1735
- autoPlural("apple", [1]); // "apple"
1736
- autoPlural("apple", [1, 2]); // "apples"
1777
+ autoPlural("element", document.querySelectorAll("html")); // "element"
1778
+ autoPlural("element", document.querySelectorAll("*")); // "elements"
1737
1779
 
1738
1780
  const items = [1, 2, 3, 4, "foo", "bar"];
1739
- console.log(`Found ${items.length} ${autoPlural("item", items)}`); // "Found 6 items"
1781
+ console.log(items.length, autoPlural("item", items)); // "6 items"
1740
1782
  ```
1741
1783
  </details>
1742
1784
 
@@ -2037,6 +2079,85 @@ benchmark(true, true); // Generated 10k in 1054ms
2037
2079
 
2038
2080
  <br><br>
2039
2081
 
2082
+ ### consumeGen()
2083
+ Usage:
2084
+ ```ts
2085
+ consumeGen(valGen: ValueGen):
2086
+ ```
2087
+
2088
+ Turns a [`ValueGen`](#valuegen) into its final value.
2089
+ ValueGen allows for tons of flexibility in how the value can be obtained. Calling this function will resolve the final value.
2090
+
2091
+ <details><summary><b>Example - click to view</b></summary>
2092
+
2093
+ ```ts
2094
+ import { consumeGen, type ValueGen } from "@sv443-network/userutils";
2095
+
2096
+ async function doSomething(value: ValueGen<number>) {
2097
+ // type gets inferred as `number` because above `value` is typed as a ValueGen<number>
2098
+ const finalValue = await consumeGen(value);
2099
+ console.log(finalValue);
2100
+ }
2101
+
2102
+ // the following are all valid and yield 42:
2103
+ doSomething(42);
2104
+ doSomething(() => 42);
2105
+ doSomething(Promise.resolve(42));
2106
+ doSomething(async () => 42);
2107
+
2108
+ // throws a typescript error:
2109
+ doSomething("foo");
2110
+ ```
2111
+
2112
+ </details>
2113
+
2114
+ <br><br>
2115
+
2116
+ ### consumeStringGen()
2117
+ Usage:
2118
+ ```ts
2119
+ consumeStringGen(strGen: StringGen): Promise<string>
2120
+ ```
2121
+
2122
+ Turns a [`StringGen`](#stringgen) into its final string value.
2123
+ StringGen allows for tons of flexibility in how the string can be obtained. Calling this function will resolve the final string.
2124
+ Optionally you can use the template parameter to define the union of strings that the StringGen should yield.
2125
+
2126
+ <details><summary><b>Example - click to view</b></summary>
2127
+
2128
+ ```ts
2129
+ import { consumeStringGen, type StringGen } from "@sv443-network/userutils";
2130
+
2131
+ export class MyTextPromptThing {
2132
+ // full flexibility on how the string can be passed to the constructor,
2133
+ // because it can be obtained synchronously or asynchronously,
2134
+ // in string or function form:
2135
+ constructor(private text: StringGen) {}
2136
+
2137
+ /** Shows the prompt dialog */
2138
+ public async showPrompt() {
2139
+ const promptText = await consumeStringGen(this.text);
2140
+ const promptHtml = promptText.trim().replace(/\n/g, "<br>");
2141
+
2142
+ // ...
2143
+ }
2144
+ }
2145
+
2146
+ // all valid:
2147
+ const myText = "Hello, World!";
2148
+ new MyTextPromptThing(myText);
2149
+ new MyTextPromptThing(() => myText);
2150
+ new MyTextPromptThing(Promise.resolve(myText));
2151
+ new MyTextPromptThing(async () => myText);
2152
+
2153
+ // throws a typescript error:
2154
+ new MyTextPromptThing(420);
2155
+ ```
2156
+
2157
+ </details>
2158
+
2159
+ <br><br>
2160
+
2040
2161
  <!-- #region Arrays -->
2041
2162
  ## Arrays:
2042
2163
 
@@ -2144,43 +2265,63 @@ Pluralization is not supported but can be achieved manually by adding variations
2144
2265
  ### tr()
2145
2266
  Usage:
2146
2267
  ```ts
2147
- tr(key: string, ...values: Stringifiable[]): string
2268
+ tr(key: string, ...insertValues: Stringifiable[]): string
2148
2269
  ```
2149
2270
 
2150
2271
  The function returns the translation of the passed key in the language added by [`tr.addLanguage()`](#traddlanguage) and set by [`tr.setLanguage()`](#trsetlanguage)
2151
- Should the translation contain placeholders in the format `%n`, where `n` is the number of the value starting at 1, they will be replaced with the respective item of the `values` rest parameter.
2152
- The items of the `values` rest parameter will be stringified using `toString()` (see [Stringifiable](#stringifiable)) before being inserted into the translation.
2272
+ Should the translation contain placeholders in the format `%n`, where `n` is the number of the value starting at 1, they will be replaced with the respective item of the `insertValues` rest parameter.
2273
+ The items of the `insertValues` rest parameter will be stringified using `toString()` (see [Stringifiable](#stringifiable)) before being inserted into the translation.
2274
+
2275
+ Should you be using nested objects in your translations, you can use the dot notation to access them.
2276
+ First, the key will be split by dots and the parts will be used to traverse the translation object.
2277
+ If that doesn't yield a result, the function will try to access the key including dots on the top level of the translation object.
2278
+ If that also doesn't yield a result, the key itself will be returned.
2279
+
2280
+ If no language has been added or set before calling this function, it will also return the key itself.
2281
+
2282
+ To check if a translation has been found, compare the returned value with the key. If they are the same, the translation was not found.
2283
+ You could also write a wrapper function that can then return a default value or `null` if the translation was not found instead.
2153
2284
 
2154
- If the key is not found or no language has been added or set before calling this function, it will return the key itself.
2155
- If the key is found and the translation contains placeholders but no values are passed, it will return the translation as-is, including unmodified placeholders.
2156
- If the key is found, the translation doesn't contain placeholders but values are still passed, they will be ignored and the translation will be returned as-is.
2285
+ If the key is found and the translation contains placeholders but none or an insufficient amount of values are passed, it will try to insert as many values as were passed and leave the rest of the placeholders untouched in their `%n` format.
2286
+ If the key is found, the translation doesn't contain placeholders but values are still passed, the values will be ignored and the translation will be returned without modification.
2157
2287
 
2158
2288
  <details><summary><b>Example - click to view</b></summary>
2159
2289
 
2160
2290
  ```ts
2161
2291
  import { tr } from "@sv443-network/userutils";
2162
2292
 
2293
+ // add languages and translations:
2163
2294
  tr.addLanguage("en", {
2164
- "welcome": "Welcome",
2165
- "welcome_name": "Welcome, %1",
2295
+ welcome: {
2296
+ generic: "Welcome",
2297
+ with_name: "Welcome, %1",
2298
+ },
2166
2299
  });
2300
+
2167
2301
  tr.addLanguage("de", {
2168
- "welcome": "Willkommen",
2169
- "welcome_name": "Willkommen, %1",
2302
+ welcome: {
2303
+ generic: "Willkommen",
2304
+ with_name: "Willkommen, %1",
2305
+ },
2170
2306
  });
2171
2307
 
2172
2308
  // this has to be called at least once before calling tr()
2173
2309
  tr.setLanguage("en");
2174
2310
 
2175
- console.log(tr("welcome")); // "Welcome"
2176
- console.log(tr("welcome_name", "John")); // "Welcome, John"
2177
- console.log(tr("non_existent_key")); // "non_existent_key"
2311
+ console.log(tr("welcome.generic")); // "Welcome"
2312
+ console.log(tr("welcome.with_name", "John")); // "Welcome, John"
2313
+
2314
+ console.log(tr("non_existent_key")); // "non_existent_key"
2315
+ console.log(tr("welcome")); // "welcome" (because anything that isn't a string will make the function return the key itself)
2178
2316
 
2179
2317
  // language can be changed at any time, synchronously
2180
2318
  tr.setLanguage("de");
2181
2319
 
2182
- console.log(tr("welcome")); // "Willkommen"
2183
- console.log(tr("welcome_name", "John")); // "Willkommen, John"
2320
+ console.log(tr("welcome.generic")); // "Willkommen"
2321
+
2322
+ // or without overwriting the current language:
2323
+
2324
+ console.log(tr.forLang("en", "welcome.generic")); // "Welcome"
2184
2325
  ```
2185
2326
  </details>
2186
2327
 
@@ -2189,7 +2330,7 @@ console.log(tr("welcome_name", "John")); // "Willkommen, John"
2189
2330
  ### tr.forLang()
2190
2331
  Usage:
2191
2332
  ```ts
2192
- tr.forLang(language: string, key: string, ...values: Stringifiable[]): string
2333
+ tr.forLang(language: string, key: string, ...insertValues: Stringifiable[]): string
2193
2334
  ```
2194
2335
 
2195
2336
  Returns the translation of the passed key in the specified language. Otherwise behaves exactly like [`tr()`](#tr)
@@ -2222,12 +2363,12 @@ console.log(tr.forLang("de", "welcome_name", "John")); // "Willkommen, John"
2222
2363
  ### tr.addLanguage()
2223
2364
  Usage:
2224
2365
  ```ts
2225
- tr.addLanguage(language: string, translations: Record<string, string>): void
2366
+ tr.addLanguage(language: string, translations: Record<string, string | object>): void
2226
2367
  ```
2227
2368
 
2228
2369
  Adds or overwrites a language and its associated translations to the translation function.
2229
2370
  The passed language can be any unique identifier, though I highly recommend sticking to a standard like [BCP 47 / RFC 5646](https://www.rfc-editor.org/rfc/rfc5646.txt) (which is used by the [`Intl` namespace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) and methods like [`Number.toLocaleString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString)), or [ISO 639-1.](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
2230
- The passed translations should be a flat object (no nested objects are allowed for now), where the key is the translation key used in `tr()` and the value is the translation itself.
2371
+ The passed translations can either be a flat object where the key is the translation key used in `tr()` and the value is the translation itself, or an infinitely nestable object structure containing the same.
2231
2372
  If `tr.addLanguage()` is called multiple times with the same language, the previous translations of that language will be overwritten.
2232
2373
 
2233
2374
  The translation values may contain placeholders in the format `%n`, where `n` is the number of the value starting at 1.
@@ -2236,42 +2377,46 @@ These can be used to inject values into the translation when calling `tr()`
2236
2377
  <details><summary><b>Example - click to view</b></summary>
2237
2378
 
2238
2379
  ```ts
2239
- import { tr } from "@sv443-network/userutils";
2380
+ import { tr, type Stringifiable } from "@sv443-network/userutils";
2240
2381
 
2241
2382
  // add a language with associated translations:
2242
2383
 
2243
- tr.addLanguage("de", {
2244
- "color": "Farbe",
2384
+ tr.addLanguage("en", {
2385
+ lang_name: "Eglis", // no worries, the example below will overwrite this value
2245
2386
  });
2246
2387
 
2247
-
2248
- // with placeholders:
2388
+ // overwriting previous translation, now with nested objects and placeholders:
2249
2389
 
2250
2390
  tr.addLanguage("en", {
2251
- "welcome_generic": "Welcome!",
2252
- "welcome_name": "Welcome, %1!",
2253
- "welcome_extended": "Welcome, %1!\nYour last login was on %2\nYou have %3 unread messages",
2391
+ // to get this value, you could call `tr.forLang("en", "lang_name")`
2392
+ lang_name: "English",
2393
+ home_page: {
2394
+ welcome: {
2395
+ generic: "Welcome!",
2396
+ // this can be accessed with `tr("home_page.welcome.name", "John")`
2397
+ name: "Welcome, %1!",
2398
+ extended: "Welcome, %1!\nYour last login was on %2\nYou have %3 unread messages",
2399
+ },
2400
+ },
2254
2401
  });
2255
2402
 
2256
-
2257
2403
  // can be used for different locales too:
2258
2404
 
2259
2405
  tr.addLanguage("en-US", {
2260
- "fries": "fries",
2261
- "color": "color",
2406
+ fries: "fries",
2407
+ color: "color",
2262
2408
  });
2263
2409
 
2264
2410
  tr.addLanguage("en-GB", {
2265
- "fries": "chips",
2266
- "color": "colour",
2411
+ fries: "chips",
2412
+ color: "colour",
2267
2413
  });
2268
2414
 
2269
-
2270
2415
  // apply default values for different locales to reduce redundancy in shared translation values:
2271
2416
 
2272
2417
  const translation_de = {
2273
- "greeting": "Guten Tag!",
2274
- "foo": "Foo",
2418
+ greeting: "Guten Tag!",
2419
+ foo: "Foo",
2275
2420
  };
2276
2421
 
2277
2422
  tr.addLanguage("de-DE", translation_de);
@@ -2279,17 +2424,16 @@ tr.addLanguage("de-DE", translation_de);
2279
2424
  tr.addLanguage("de-CH", {
2280
2425
  // overwrite the "greeting" but keep other keys as they are:
2281
2426
  ...translation_de,
2282
- "greeting": "Grüezi!",
2427
+ greeting: "Grüezi!",
2283
2428
  });
2284
2429
 
2285
2430
  tr.addLanguage("de-AT", {
2286
2431
  // overwrite "greeting" again but keep other keys as they are:
2287
2432
  ...translation_de,
2288
- "greeting": "Grüß Gott!",
2433
+ greeting: "Grüß Gott!",
2289
2434
  });
2290
2435
 
2291
-
2292
- // example for custom pluralization:
2436
+ // example for custom pluralization using a predefined suffix:
2293
2437
 
2294
2438
  tr.addLanguage("en", {
2295
2439
  "cart_items_added-0": "No items were added to the cart",
@@ -2297,10 +2441,14 @@ tr.addLanguage("en", {
2297
2441
  "cart_items_added-n": "Added %1 items to the cart",
2298
2442
  });
2299
2443
 
2444
+ /** A number or any object with a length or size property */
2300
2445
  type Numberish = number | Array<unknown> | NodeList | { length: number } | { size: number };
2301
2446
 
2302
- /** Returns the translation key with a custom pluralization identifier added to it for the given number of items (or size of Array/NodeList or anything else with a `length` or `size` property) */
2303
- function pl(key: string, num: Numberish): string {
2447
+ /**
2448
+ * Returns the translated value given the key with a common pluralization identifier appended to it,
2449
+ * given the number of items (or size of Array/NodeList or anything else with a `length` or `size` property).
2450
+ */
2451
+ function trpl(key: string, num: Numberish, ...values: Stringifiable[]): string {
2304
2452
  if(typeof num !== "number") {
2305
2453
  if("length" in num)
2306
2454
  num = num.length;
@@ -2308,25 +2456,32 @@ function pl(key: string, num: Numberish): string {
2308
2456
  num = num.size;
2309
2457
  }
2310
2458
 
2459
+ let plKey = key;
2311
2460
  if(num === 0)
2312
- return `${key}-0`;
2461
+ plKey = `${key}-0`;
2313
2462
  else if(num === 1)
2314
- return `${key}-1`;
2463
+ plKey = `${key}-1`;
2315
2464
  else
2316
- return `${key}-n`; // will also be the fallback for non-numeric values
2465
+ plKey = `${key}-n`; // will be the fallback for everything like non-numeric values or NaN
2466
+
2467
+ return tr(plKey, ...values);
2317
2468
  };
2318
2469
 
2470
+ // this has to be called once for tr("key") to work - otherwise you can use tr.forLang("en", "key")
2471
+ tr.setLanguage("en");
2472
+
2319
2473
  const items = [];
2320
- console.log(tr(pl("cart_items_added", items), items.length)); // "No items were added to the cart"
2474
+ console.log(trpl("cart_items_added", items, items.length)); // "No items were added to the cart"
2321
2475
 
2322
2476
  items.push("foo");
2323
- console.log(tr(pl("cart_items_added", items), items.length)); // "Added 1 item to the cart"
2477
+ console.log(trpl("cart_items_added", items, items.length)); // "Added 1 item to the cart"
2324
2478
 
2325
2479
  items.push("bar");
2326
- console.log(tr(pl("cart_items_added", items), items.length)); // "Added 2 items to the cart"
2480
+ console.log(trpl("cart_items_added", items, items.length)); // "Added 2 items to the cart"
2327
2481
 
2328
- // you will need to catch cases like this manually or in your own implementation of `pl()`:
2329
- console.log(tr(pl("cart_items_added", NaN), NaN)); // "Added NaN items to the cart"
2482
+ // if you run across cases like this, you need to modify your implementation of `trpl()` accordingly:
2483
+ const someVal = parseInt("not a number");
2484
+ console.log(trpl("cart_items_added", someVal, someVal)); // "Added NaN items to the cart"
2330
2485
  ```
2331
2486
  </details>
2332
2487
 
@@ -2338,10 +2493,11 @@ Usage:
2338
2493
  tr.setLanguage(language: string): void
2339
2494
  ```
2340
2495
 
2341
- Synchronously sets the language that will be used for translations.
2496
+ Synchronously sets the language that will be used for translations by default.
2497
+ Alternatively, you can use [`tr.forLang()`](#trforlang) to get translations in a different language without changing the current language.
2342
2498
  No validation is done on the passed language, so make sure it is correct and it has been added with `tr.addLanguage()` before calling `tr()`
2343
2499
 
2344
- For an example, see [`tr()`](#tr)
2500
+ For an example, please see [`tr()`](#tr)
2345
2501
 
2346
2502
  <br>
2347
2503
 
@@ -2359,7 +2515,7 @@ If no language has been set yet, it will return undefined.
2359
2515
  ### tr.getTranslations()
2360
2516
  Usage:
2361
2517
  ```ts
2362
- tr.getTranslations(language?: string): Record<string, string> | undefined
2518
+ tr.getTranslations(language?: string): Record<string, string | object> | undefined
2363
2519
  ```
2364
2520
 
2365
2521
  Returns the translations of the specified language.
@@ -2372,7 +2528,7 @@ If no translations are found, it will return undefined.
2372
2528
  import { tr } from "@sv443-network/userutils";
2373
2529
 
2374
2530
  tr.addLanguage("en", {
2375
- "welcome": "Welcome",
2531
+ welcome: "Welcome",
2376
2532
  });
2377
2533
 
2378
2534
  console.log(tr.getTranslations()); // undefined
@@ -2528,7 +2684,7 @@ logSomething(barObject); // Type error
2528
2684
 
2529
2685
  <br>
2530
2686
 
2531
- ## NonEmptyArray
2687
+ ### NonEmptyArray
2532
2688
  Usage:
2533
2689
  ```ts
2534
2690
  NonEmptyArray<TItem = unknown>
@@ -2558,7 +2714,7 @@ logFirstItem(["04abc", "69"]); // 4
2558
2714
 
2559
2715
  <br>
2560
2716
 
2561
- ## NonEmptyString
2717
+ ### NonEmptyString
2562
2718
  Usage:
2563
2719
  ```ts
2564
2720
  NonEmptyString<TString extends string>
@@ -2582,7 +2738,7 @@ convertToNumber(""); // type error: Argument of type 'string' is not assign
2582
2738
 
2583
2739
  <br>
2584
2740
 
2585
- ## LooseUnion
2741
+ ### LooseUnion
2586
2742
  Usage:
2587
2743
  ```ts
2588
2744
  LooseUnion<TUnion extends string | number | object>
@@ -2609,7 +2765,7 @@ foo(1); // type error: Argument of type '1' is not assignable to parameter of
2609
2765
 
2610
2766
  <br>
2611
2767
 
2612
- ## Prettify
2768
+ ### Prettify
2613
2769
  Usage:
2614
2770
  ```ts
2615
2771
  Prettify<T>
@@ -2662,6 +2818,29 @@ const bar: Bar = {
2662
2818
  ```
2663
2819
  </details>
2664
2820
 
2821
+ <br><br>
2822
+
2823
+ ### ValueGen
2824
+ Usage:
2825
+ ```ts
2826
+ ValueGen<TValueType>
2827
+ ```
2828
+
2829
+ Describes a value that can be obtained in various ways, including via the type itself, a function that returns the type, a Promise that resolves to the type or either a sync or an async function that returns the type.
2830
+ Use it in the [`consumeGen()`](#consumegen) function to convert the given ValueGen value to the type it represents. Also refer to that function for an example.
2831
+
2832
+ <br><br>
2833
+
2834
+ ### StringGen
2835
+ Usage:
2836
+ ```ts
2837
+ StringGen<TStrUnion>
2838
+ ```
2839
+
2840
+ Describes a string that can be obtained in various ways, including via the type itself, a function that returns the type, a Promise that resolves to the type or either a sync or an async function that returns the type.
2841
+ Contrary to [`ValueGen`](#valuegen), this type allows for specifying a union of strings that the StringGen should yield, as long as it is loosely typed as just `string`.
2842
+ Use it in the [`consumeStringGen()`](#consumestringgen) function to convert the given StringGen value to a plain string. Also refer to that function for an example.
2843
+
2665
2844
  <br><br><br><br>
2666
2845
 
2667
2846
  <!-- #region Footer -->
package/dist/index.cjs CHANGED
@@ -30,10 +30,7 @@ var __objRest = (source, exclude) => {
30
30
  }
31
31
  return target;
32
32
  };
33
- var __publicField = (obj, key, value) => {
34
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
35
- return value;
36
- };
33
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
37
34
  var __async = (__this, __arguments, generator) => {
38
35
  return new Promise((resolve, reject) => {
39
36
  var fulfilled = (value) => {
@@ -57,14 +54,17 @@ var __async = (__this, __arguments, generator) => {
57
54
 
58
55
  // lib/math.ts
59
56
  function clamp(value, min, max) {
57
+ if (typeof max !== "number") {
58
+ max = min;
59
+ min = 0;
60
+ }
60
61
  return Math.max(Math.min(value, max), min);
61
62
  }
62
63
  function mapRange(value, range1min, range1max, range2min, range2max) {
63
- if (typeof range2min === "undefined" || range2max === void 0) {
64
+ if (typeof range2min === "undefined" || typeof range2max === "undefined") {
64
65
  range2max = range1max;
65
- range2min = 0;
66
66
  range1max = range1min;
67
- range1min = 0;
67
+ range2min = range1min = 0;
68
68
  }
69
69
  if (Number(range1min) === 0 && Number(range2min) === 0)
70
70
  return value * (range2max / range1max);
@@ -78,7 +78,7 @@ function randRange(...args) {
78
78
  min = 0;
79
79
  [max] = args;
80
80
  } else
81
- throw new TypeError(`Wrong parameter(s) provided - expected: "number" and "number|undefined", got: "${typeof args[0]}" and "${typeof args[1]}"`);
81
+ throw new TypeError(`Wrong parameter(s) provided - expected (number, boolean|undefined) or (number, number, boolean|undefined) but got (${args.map((a) => typeof a).join(", ")}) instead`);
82
82
  if (typeof args[2] === "boolean")
83
83
  enhancedEntropy = args[2];
84
84
  else if (typeof args[1] === "boolean")
@@ -99,6 +99,14 @@ function randRange(...args) {
99
99
  } else
100
100
  return Math.floor(Math.random() * (max - min + 1)) + min;
101
101
  }
102
+ function digitCount(num) {
103
+ num = Number(!["string", "number"].includes(typeof num) ? String(num) : num);
104
+ if (typeof num === "number" && isNaN(num))
105
+ return NaN;
106
+ return num === 0 ? 1 : Math.floor(
107
+ Math.log10(Math.abs(Number(num))) + 1
108
+ );
109
+ }
102
110
 
103
111
  // lib/array.ts
104
112
  function randomItem(array) {
@@ -1143,8 +1151,7 @@ var Dialog = class _Dialog extends NanoEmitter {
1143
1151
  if (interactionKeys.includes(e.key)) {
1144
1152
  preventDefault && e.preventDefault();
1145
1153
  stopPropagation && e.stopPropagation();
1146
- } else
1147
- return;
1154
+ } else return;
1148
1155
  } else if (e instanceof MouseEvent) {
1149
1156
  preventDefault && e.preventDefault();
1150
1157
  stopPropagation && e.stopPropagation();
@@ -1270,6 +1277,18 @@ function fetchAdvanced(_0) {
1270
1277
  }
1271
1278
  });
1272
1279
  }
1280
+ function consumeGen(valGen) {
1281
+ return __async(this, null, function* () {
1282
+ return yield typeof valGen === "function" ? valGen() : valGen;
1283
+ });
1284
+ }
1285
+ function consumeStringGen(strGen) {
1286
+ return __async(this, null, function* () {
1287
+ return typeof strGen === "string" ? strGen : String(
1288
+ typeof strGen === "function" ? yield strGen() : strGen
1289
+ );
1290
+ });
1291
+ }
1273
1292
 
1274
1293
  // lib/SelectorObserver.ts
1275
1294
  var domLoaded = false;
@@ -1449,23 +1468,28 @@ var SelectorObserver = class {
1449
1468
 
1450
1469
  // lib/translation.ts
1451
1470
  var trans = {};
1452
- var curLang;
1453
- var trLang = (language, key, ...args) => {
1471
+ var curLang = "";
1472
+ function translate(language, key, ...args) {
1454
1473
  var _a;
1455
- if (!language)
1474
+ const trObj = (_a = trans[language]) == null ? void 0 : _a.data;
1475
+ if (typeof language !== "string" || typeof trObj !== "object" || trObj === null)
1456
1476
  return key;
1457
- const trText = (_a = trans[language]) == null ? void 0 : _a[key];
1458
- if (!trText)
1459
- return key;
1460
- if (args.length > 0 && trText.match(/%\d/)) {
1461
- return insertValues(trText, ...args);
1477
+ const keyParts = key.split(".");
1478
+ let value = trObj;
1479
+ for (const part of keyParts) {
1480
+ if (typeof value !== "object" || value === null)
1481
+ break;
1482
+ value = value == null ? void 0 : value[part];
1462
1483
  }
1463
- return trText;
1464
- };
1465
- function tr(key, ...args) {
1466
- return trLang(curLang, key, ...args);
1484
+ if (typeof value === "string")
1485
+ return insertValues(value, args);
1486
+ value = trObj == null ? void 0 : trObj[key];
1487
+ if (typeof value === "string")
1488
+ return insertValues(value, args);
1489
+ return key;
1467
1490
  }
1468
- tr.forLang = trLang;
1491
+ var tr = (key, ...args) => translate(curLang, key, ...args);
1492
+ tr.forLang = translate;
1469
1493
  tr.addLanguage = (language, translations) => {
1470
1494
  trans[language] = translations;
1471
1495
  };
@@ -1490,11 +1514,14 @@ exports.autoPlural = autoPlural;
1490
1514
  exports.clamp = clamp;
1491
1515
  exports.compress = compress;
1492
1516
  exports.computeHash = computeHash;
1517
+ exports.consumeGen = consumeGen;
1518
+ exports.consumeStringGen = consumeStringGen;
1493
1519
  exports.darkenColor = darkenColor;
1494
1520
  exports.debounce = debounce;
1495
1521
  exports.decompress = decompress;
1496
1522
  exports.defaultDialogCss = defaultDialogCss;
1497
1523
  exports.defaultStrings = defaultStrings;
1524
+ exports.digitCount = digitCount;
1498
1525
  exports.fetchAdvanced = fetchAdvanced;
1499
1526
  exports.getSiblingsFrame = getSiblingsFrame;
1500
1527
  exports.getUnsafeWindow = getUnsafeWindow;
@@ -8,7 +8,7 @@
8
8
  // ==UserLibrary==
9
9
  // @name UserUtils
10
10
  // @description Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more
11
- // @version 8.3.3
11
+ // @version 8.4.0
12
12
  // @license MIT
13
13
  // @copyright Sv443 (https://github.com/Sv443)
14
14
 
@@ -48,10 +48,7 @@ var UserUtils = (function (exports) {
48
48
  }
49
49
  return target;
50
50
  };
51
- var __publicField = (obj, key, value) => {
52
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
53
- return value;
54
- };
51
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
55
52
  var __async = (__this, __arguments, generator) => {
56
53
  return new Promise((resolve, reject) => {
57
54
  var fulfilled = (value) => {
@@ -75,14 +72,17 @@ var UserUtils = (function (exports) {
75
72
 
76
73
  // lib/math.ts
77
74
  function clamp(value, min, max) {
75
+ if (typeof max !== "number") {
76
+ max = min;
77
+ min = 0;
78
+ }
78
79
  return Math.max(Math.min(value, max), min);
79
80
  }
80
81
  function mapRange(value, range1min, range1max, range2min, range2max) {
81
- if (typeof range2min === "undefined" || range2max === void 0) {
82
+ if (typeof range2min === "undefined" || typeof range2max === "undefined") {
82
83
  range2max = range1max;
83
- range2min = 0;
84
84
  range1max = range1min;
85
- range1min = 0;
85
+ range2min = range1min = 0;
86
86
  }
87
87
  if (Number(range1min) === 0 && Number(range2min) === 0)
88
88
  return value * (range2max / range1max);
@@ -96,7 +96,7 @@ var UserUtils = (function (exports) {
96
96
  min = 0;
97
97
  [max] = args;
98
98
  } else
99
- throw new TypeError(`Wrong parameter(s) provided - expected: "number" and "number|undefined", got: "${typeof args[0]}" and "${typeof args[1]}"`);
99
+ throw new TypeError(`Wrong parameter(s) provided - expected (number, boolean|undefined) or (number, number, boolean|undefined) but got (${args.map((a) => typeof a).join(", ")}) instead`);
100
100
  if (typeof args[2] === "boolean")
101
101
  enhancedEntropy = args[2];
102
102
  else if (typeof args[1] === "boolean")
@@ -117,6 +117,14 @@ var UserUtils = (function (exports) {
117
117
  } else
118
118
  return Math.floor(Math.random() * (max - min + 1)) + min;
119
119
  }
120
+ function digitCount(num) {
121
+ num = Number(!["string", "number"].includes(typeof num) ? String(num) : num);
122
+ if (typeof num === "number" && isNaN(num))
123
+ return NaN;
124
+ return num === 0 ? 1 : Math.floor(
125
+ Math.log10(Math.abs(Number(num))) + 1
126
+ );
127
+ }
120
128
 
121
129
  // lib/array.ts
122
130
  function randomItem(array) {
@@ -766,10 +774,10 @@ Has: ${checksum}`);
766
774
  }
767
775
  };
768
776
 
769
- // node_modules/nanoevents/index.js
777
+ // node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js
770
778
  var createNanoEvents = () => ({
771
779
  emit(event, ...args) {
772
- for (let i = 0, callbacks = this.events[event] || [], length = callbacks.length; i < length; i++) {
780
+ for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) {
773
781
  callbacks[i](...args);
774
782
  }
775
783
  },
@@ -1181,8 +1189,7 @@ Has: ${checksum}`);
1181
1189
  if (interactionKeys.includes(e.key)) {
1182
1190
  preventDefault && e.preventDefault();
1183
1191
  stopPropagation && e.stopPropagation();
1184
- } else
1185
- return;
1192
+ } else return;
1186
1193
  } else if (e instanceof MouseEvent) {
1187
1194
  preventDefault && e.preventDefault();
1188
1195
  stopPropagation && e.stopPropagation();
@@ -1308,6 +1315,18 @@ Has: ${checksum}`);
1308
1315
  }
1309
1316
  });
1310
1317
  }
1318
+ function consumeGen(valGen) {
1319
+ return __async(this, null, function* () {
1320
+ return yield typeof valGen === "function" ? valGen() : valGen;
1321
+ });
1322
+ }
1323
+ function consumeStringGen(strGen) {
1324
+ return __async(this, null, function* () {
1325
+ return typeof strGen === "string" ? strGen : String(
1326
+ typeof strGen === "function" ? yield strGen() : strGen
1327
+ );
1328
+ });
1329
+ }
1311
1330
 
1312
1331
  // lib/SelectorObserver.ts
1313
1332
  var domLoaded = false;
@@ -1487,23 +1506,28 @@ Has: ${checksum}`);
1487
1506
 
1488
1507
  // lib/translation.ts
1489
1508
  var trans = {};
1490
- var curLang;
1491
- var trLang = (language, key, ...args) => {
1509
+ var curLang = "";
1510
+ function translate(language, key, ...args) {
1492
1511
  var _a;
1493
- if (!language)
1494
- return key;
1495
- const trText = (_a = trans[language]) == null ? void 0 : _a[key];
1496
- if (!trText)
1512
+ const trObj = (_a = trans[language]) == null ? void 0 : _a.data;
1513
+ if (typeof language !== "string" || typeof trObj !== "object" || trObj === null)
1497
1514
  return key;
1498
- if (args.length > 0 && trText.match(/%\d/)) {
1499
- return insertValues(trText, ...args);
1500
- }
1501
- return trText;
1502
- };
1503
- function tr(key, ...args) {
1504
- return trLang(curLang, key, ...args);
1515
+ const keyParts = key.split(".");
1516
+ let value = trObj;
1517
+ for (const part of keyParts) {
1518
+ if (typeof value !== "object" || value === null)
1519
+ break;
1520
+ value = value == null ? void 0 : value[part];
1521
+ }
1522
+ if (typeof value === "string")
1523
+ return insertValues(value, args);
1524
+ value = trObj == null ? void 0 : trObj[key];
1525
+ if (typeof value === "string")
1526
+ return insertValues(value, args);
1527
+ return key;
1505
1528
  }
1506
- tr.forLang = trLang;
1529
+ var tr = (key, ...args) => translate(curLang, key, ...args);
1530
+ tr.forLang = translate;
1507
1531
  tr.addLanguage = (language, translations) => {
1508
1532
  trans[language] = translations;
1509
1533
  };
@@ -1528,11 +1552,14 @@ Has: ${checksum}`);
1528
1552
  exports.clamp = clamp;
1529
1553
  exports.compress = compress;
1530
1554
  exports.computeHash = computeHash;
1555
+ exports.consumeGen = consumeGen;
1556
+ exports.consumeStringGen = consumeStringGen;
1531
1557
  exports.darkenColor = darkenColor;
1532
1558
  exports.debounce = debounce;
1533
1559
  exports.decompress = decompress;
1534
1560
  exports.defaultDialogCss = defaultDialogCss;
1535
1561
  exports.defaultStrings = defaultStrings;
1562
+ exports.digitCount = digitCount;
1536
1563
  exports.fetchAdvanced = fetchAdvanced;
1537
1564
  exports.getSiblingsFrame = getSiblingsFrame;
1538
1565
  exports.getUnsafeWindow = getUnsafeWindow;
package/dist/index.js CHANGED
@@ -28,10 +28,7 @@ var __objRest = (source, exclude) => {
28
28
  }
29
29
  return target;
30
30
  };
31
- var __publicField = (obj, key, value) => {
32
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
33
- return value;
34
- };
31
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
35
32
  var __async = (__this, __arguments, generator) => {
36
33
  return new Promise((resolve, reject) => {
37
34
  var fulfilled = (value) => {
@@ -55,14 +52,17 @@ var __async = (__this, __arguments, generator) => {
55
52
 
56
53
  // lib/math.ts
57
54
  function clamp(value, min, max) {
55
+ if (typeof max !== "number") {
56
+ max = min;
57
+ min = 0;
58
+ }
58
59
  return Math.max(Math.min(value, max), min);
59
60
  }
60
61
  function mapRange(value, range1min, range1max, range2min, range2max) {
61
- if (typeof range2min === "undefined" || range2max === void 0) {
62
+ if (typeof range2min === "undefined" || typeof range2max === "undefined") {
62
63
  range2max = range1max;
63
- range2min = 0;
64
64
  range1max = range1min;
65
- range1min = 0;
65
+ range2min = range1min = 0;
66
66
  }
67
67
  if (Number(range1min) === 0 && Number(range2min) === 0)
68
68
  return value * (range2max / range1max);
@@ -76,7 +76,7 @@ function randRange(...args) {
76
76
  min = 0;
77
77
  [max] = args;
78
78
  } else
79
- throw new TypeError(`Wrong parameter(s) provided - expected: "number" and "number|undefined", got: "${typeof args[0]}" and "${typeof args[1]}"`);
79
+ throw new TypeError(`Wrong parameter(s) provided - expected (number, boolean|undefined) or (number, number, boolean|undefined) but got (${args.map((a) => typeof a).join(", ")}) instead`);
80
80
  if (typeof args[2] === "boolean")
81
81
  enhancedEntropy = args[2];
82
82
  else if (typeof args[1] === "boolean")
@@ -97,6 +97,14 @@ function randRange(...args) {
97
97
  } else
98
98
  return Math.floor(Math.random() * (max - min + 1)) + min;
99
99
  }
100
+ function digitCount(num) {
101
+ num = Number(!["string", "number"].includes(typeof num) ? String(num) : num);
102
+ if (typeof num === "number" && isNaN(num))
103
+ return NaN;
104
+ return num === 0 ? 1 : Math.floor(
105
+ Math.log10(Math.abs(Number(num))) + 1
106
+ );
107
+ }
100
108
 
101
109
  // lib/array.ts
102
110
  function randomItem(array) {
@@ -1141,8 +1149,7 @@ var Dialog = class _Dialog extends NanoEmitter {
1141
1149
  if (interactionKeys.includes(e.key)) {
1142
1150
  preventDefault && e.preventDefault();
1143
1151
  stopPropagation && e.stopPropagation();
1144
- } else
1145
- return;
1152
+ } else return;
1146
1153
  } else if (e instanceof MouseEvent) {
1147
1154
  preventDefault && e.preventDefault();
1148
1155
  stopPropagation && e.stopPropagation();
@@ -1268,6 +1275,18 @@ function fetchAdvanced(_0) {
1268
1275
  }
1269
1276
  });
1270
1277
  }
1278
+ function consumeGen(valGen) {
1279
+ return __async(this, null, function* () {
1280
+ return yield typeof valGen === "function" ? valGen() : valGen;
1281
+ });
1282
+ }
1283
+ function consumeStringGen(strGen) {
1284
+ return __async(this, null, function* () {
1285
+ return typeof strGen === "string" ? strGen : String(
1286
+ typeof strGen === "function" ? yield strGen() : strGen
1287
+ );
1288
+ });
1289
+ }
1271
1290
 
1272
1291
  // lib/SelectorObserver.ts
1273
1292
  var domLoaded = false;
@@ -1447,23 +1466,28 @@ var SelectorObserver = class {
1447
1466
 
1448
1467
  // lib/translation.ts
1449
1468
  var trans = {};
1450
- var curLang;
1451
- var trLang = (language, key, ...args) => {
1469
+ var curLang = "";
1470
+ function translate(language, key, ...args) {
1452
1471
  var _a;
1453
- if (!language)
1472
+ const trObj = (_a = trans[language]) == null ? void 0 : _a.data;
1473
+ if (typeof language !== "string" || typeof trObj !== "object" || trObj === null)
1454
1474
  return key;
1455
- const trText = (_a = trans[language]) == null ? void 0 : _a[key];
1456
- if (!trText)
1457
- return key;
1458
- if (args.length > 0 && trText.match(/%\d/)) {
1459
- return insertValues(trText, ...args);
1475
+ const keyParts = key.split(".");
1476
+ let value = trObj;
1477
+ for (const part of keyParts) {
1478
+ if (typeof value !== "object" || value === null)
1479
+ break;
1480
+ value = value == null ? void 0 : value[part];
1460
1481
  }
1461
- return trText;
1462
- };
1463
- function tr(key, ...args) {
1464
- return trLang(curLang, key, ...args);
1482
+ if (typeof value === "string")
1483
+ return insertValues(value, args);
1484
+ value = trObj == null ? void 0 : trObj[key];
1485
+ if (typeof value === "string")
1486
+ return insertValues(value, args);
1487
+ return key;
1465
1488
  }
1466
- tr.forLang = trLang;
1489
+ var tr = (key, ...args) => translate(curLang, key, ...args);
1490
+ tr.forLang = translate;
1467
1491
  tr.addLanguage = (language, translations) => {
1468
1492
  trans[language] = translations;
1469
1493
  };
@@ -1477,4 +1501,4 @@ tr.getTranslations = (language) => {
1477
1501
  return trans[language != null ? language : curLang];
1478
1502
  };
1479
1503
 
1480
- export { DataStore, DataStoreSerializer, Dialog, NanoEmitter, SelectorObserver, addGlobalStyle, addParent, autoPlural, clamp, compress, computeHash, currentDialogId, darkenColor, debounce, decompress, defaultDialogCss, defaultStrings, fetchAdvanced, getSiblingsFrame, getUnsafeWindow, hexToRgb, insertValues, interceptEvent, interceptWindowEvent, isScrollable, lightenColor, mapRange, observeElementProp, openDialogs, openInNewTab, pauseFor, preloadImages, randRange, randomId, randomItem, randomItemIndex, randomizeArray, rgbToHex, setInnerHtmlUnsafe, takeRandomItem, tr };
1504
+ export { DataStore, DataStoreSerializer, Dialog, NanoEmitter, SelectorObserver, addGlobalStyle, addParent, autoPlural, clamp, compress, computeHash, consumeGen, consumeStringGen, currentDialogId, darkenColor, debounce, decompress, defaultDialogCss, defaultStrings, digitCount, fetchAdvanced, getSiblingsFrame, getUnsafeWindow, hexToRgb, insertValues, interceptEvent, interceptWindowEvent, isScrollable, lightenColor, mapRange, observeElementProp, openDialogs, openInNewTab, pauseFor, preloadImages, randRange, randomId, randomItem, randomItemIndex, randomizeArray, rgbToHex, setInnerHtmlUnsafe, takeRandomItem, tr };
@@ -1,4 +1,3 @@
1
- /// <reference types="greasemonkey" />
2
1
  import type { Prettify } from "./types.js";
3
2
  /** Function that takes the data in the old format and returns the data in the new format. Also supports an asynchronous migration. */
4
3
  type MigrationFunc = (oldData: any) => any | Promise<any>;
@@ -1,5 +1,8 @@
1
+ import type { Stringifiable } from "./types.js";
1
2
  /** Ensures the passed {@linkcode value} always stays between {@linkcode min} and {@linkcode max} */
2
3
  export declare function clamp(value: number, min: number, max: number): number;
4
+ /** Ensures the passed {@linkcode value} always stays between 0 and {@linkcode max} */
5
+ export declare function clamp(value: number, max: number): number;
3
6
  /**
4
7
  * Transforms the value parameter from the numerical range `range1min` to `range1max` to the numerical range `range2min` to `range2max`
5
8
  * For example, you can map the value 2 in the range of 0-5 to the range of 0-10 and you'd get a 4 as a result.
@@ -20,3 +23,5 @@ export declare function randRange(min: number, max: number, enhancedEntropy?: bo
20
23
  * Set {@linkcode enhancedEntropy} to true to use `crypto.getRandomValues()` for better cryptographic randomness (this also makes it take longer to generate)
21
24
  */
22
25
  export declare function randRange(max: number, enhancedEntropy?: boolean): number;
26
+ /** Calculates the amount of digits in the given number - the given number or string will be passed to the `Number()` constructor. Returns NaN if the number is invalid. */
27
+ export declare function digitCount(num: number | Stringifiable): number;
@@ -29,4 +29,25 @@ export type FetchAdvancedOpts = Prettify<Partial<{
29
29
  timeout: number;
30
30
  }> & RequestInit>;
31
31
  /** Calls the fetch API with special options like a timeout */
32
- export declare function fetchAdvanced(input: RequestInfo | URL, options?: FetchAdvancedOpts): Promise<Response>;
32
+ export declare function fetchAdvanced(input: string | RequestInfo | URL, options?: FetchAdvancedOpts): Promise<Response>;
33
+ /**
34
+ * A ValueGen value is either its type, a promise that resolves to its type, or a function that returns its type, either synchronous or asynchronous.
35
+ * ValueGen allows for the utmost flexibility when applied to any type, as long as {@linkcode consumeGen()} is used to get the final value.
36
+ * @template TValueType The type of the value that the ValueGen should yield
37
+ */
38
+ export type ValueGen<TValueType> = TValueType | Promise<TValueType> | (() => TValueType | Promise<TValueType>);
39
+ /**
40
+ * Turns a {@linkcode ValueGen} into its final value.
41
+ * @template TValueType The type of the value that the ValueGen should yield
42
+ */
43
+ export declare function consumeGen<TValueType>(valGen: ValueGen<TValueType>): Promise<TValueType>;
44
+ /**
45
+ * A StringGen value is either a string, anything that can be converted to a string, or a function that returns one of the previous two, either synchronous or asynchronous, or a promise that returns a string.
46
+ * StringGen allows for the utmost flexibility when dealing with strings, as long as {@linkcode consumeStringGen()} is used to get the final string.
47
+ */
48
+ export type StringGen = ValueGen<Stringifiable>;
49
+ /**
50
+ * Turns a {@linkcode StringGen} into its final string value.
51
+ * @template TStrUnion The union of strings that the StringGen should yield - this allows for finer type control compared to {@linkcode consumeGen()}
52
+ */
53
+ export declare function consumeStringGen<TStrUnion extends string>(strGen: StringGen): Promise<TStrUnion>;
@@ -1,4 +1,28 @@
1
1
  import type { Stringifiable } from "./types.js";
2
+ /**
3
+ * Translation object to pass to {@linkcode tr.addLanguage()}
4
+ * Can be a flat object of identifier keys and the translation text as the value, or an infinitely nestable object containing the same.
5
+ *
6
+ * @example
7
+ * // Flat object:
8
+ * const tr_en: TrObject = {
9
+ * hello: "Hello, %1!",
10
+ * foo: "Foo",
11
+ * };
12
+ *
13
+ * // Nested object:
14
+ * const tr_de: TrObject = {
15
+ * hello: "Hallo, %1!",
16
+ * foo: {
17
+ * bar: "Foo bar",
18
+ * },
19
+ * };
20
+ */
21
+ export interface TrObject {
22
+ [key: string]: string | TrObject;
23
+ }
24
+ /** Common function to resolve the translation text in a specific language. */
25
+ declare function translate(language: string, key: string, ...args: Stringifiable[]): string;
2
26
  /**
3
27
  * Returns the translated text for the specified key in the current language set by {@linkcode tr.setLanguage()}
4
28
  * Use {@linkcode tr.forLang()} to get the translation for a specific language instead of the currently set one.
@@ -8,12 +32,21 @@ import type { Stringifiable } from "./types.js";
8
32
  * @param key Key of the translation to return
9
33
  * @param args Optional arguments to be passed to the translated text. They will replace placeholders in the format `%n`, where `n` is the 1-indexed argument number
10
34
  */
11
- declare function tr(key: string, ...args: Stringifiable[]): string;
12
- declare namespace tr {
13
- var forLang: (language: string, key: string, ...args: Stringifiable[]) => string;
14
- var addLanguage: (language: string, translations: Record<string, string>) => void;
15
- var setLanguage: (language: string) => void;
16
- var getLanguage: () => string;
17
- var getTranslations: (language?: string | undefined) => Record<string, string> | undefined;
18
- }
35
+ declare const tr: {
36
+ (key: string, ...args: Stringifiable[]): string;
37
+ /**
38
+ * Returns the translated text for the specified key in the specified language.
39
+ * If the key is not found in the specified previously registered translation, the key itself is returned.
40
+ *
41
+ * ⚠️ Remember to register a language with {@linkcode tr.addLanguage()} before using this function, otherwise it will always return the key itself.
42
+ * @param language Language to use for the translation
43
+ * @param key Key of the translation to return
44
+ * @param args Optional arguments to be passed to the translated text. They will replace placeholders in the format `%n`, where `n` is the 1-indexed argument number
45
+ */
46
+ forLang: typeof translate;
47
+ addLanguage(language: string, translations: TrObject): void;
48
+ setLanguage(language: string): void;
49
+ getLanguage(): string;
50
+ getTranslations(language?: string): TrObject | undefined;
51
+ };
19
52
  export { tr };
@@ -1,7 +1,9 @@
1
1
  /** Represents any value that is either a string itself or can be converted to one (implicitly and explicitly) because it has a toString() method */
2
2
  export type Stringifiable = string | {
3
3
  toString(): string;
4
- };
4
+ } | {
5
+ [Symbol.toStringTag]: string;
6
+ } | number | boolean | null | undefined;
5
7
  /**
6
8
  * A type that offers autocomplete for the passed union but also allows any arbitrary value of the same type to be passed.
7
9
  * Supports unions of strings, numbers and objects.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sv443-network/userutils",
3
3
  "libName": "UserUtils",
4
- "version": "8.3.3",
4
+ "version": "8.4.0",
5
5
  "description": "Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -15,24 +15,6 @@
15
15
  }
16
16
  },
17
17
  "type": "module",
18
- "scripts": {
19
- "lint": "tsc --noEmit && eslint .",
20
- "build-types": "tsc --emitDeclarationOnly --declaration --outDir dist",
21
- "build-common": "tsup lib/index.ts --format cjs,esm --clean --treeshake",
22
- "build-all": "tsup lib/index.ts --format cjs,esm,iife --treeshake --onSuccess \"npm run build-types && npm run post-build-global && echo Finished building.\"",
23
- "build": "npm run build-common -- && npm run build-types",
24
- "post-build-global": "npm run node-ts -- ./tools/post-build-global.mts",
25
- "dev": "npm run build-common -- --sourcemap --watch --onSuccess \"npm run build-types && echo Finished building.\"",
26
- "dev-all": "npm run build-all -- --watch",
27
- "update-jsr-version": "npm run node-ts -- ./tools/update-jsr-version.mts",
28
- "publish-package": "changeset publish",
29
- "publish-package-jsr": "npm run update-jsr-version && npx jsr publish --allow-dirty",
30
- "change": "changeset",
31
- "node-ts": "node --no-warnings=ExperimentalWarning --enable-source-maps --loader ts-node/esm",
32
- "test-serve": "npm run node-ts -- ./test/TestPage/server.mts",
33
- "test-dev": "cd test/TestScript && npm run dev",
34
- "test": "concurrently \"npm run test-serve\" \"npm run test-dev\""
35
- },
36
18
  "repository": {
37
19
  "type": "git",
38
20
  "url": "git+https://github.com/Sv443-Network/UserUtils.git"
@@ -51,22 +33,22 @@
51
33
  },
52
34
  "homepage": "https://github.com/Sv443-Network/UserUtils",
53
35
  "dependencies": {
54
- "nanoevents": "^9.0.0"
36
+ "nanoevents": "^9.1.0"
55
37
  },
56
38
  "devDependencies": {
57
- "@changesets/cli": "^2.26.2",
58
- "@types/express": "^4.17.19",
59
- "@types/greasemonkey": "^4.0.4",
60
- "@types/node": "^20.5.9",
61
- "@typescript-eslint/eslint-plugin": "^6.2.1",
62
- "@typescript-eslint/parser": "^6.2.1",
63
- "concurrently": "^8.2.1",
64
- "eslint": "^8.46.0",
65
- "express": "^4.18.2",
66
- "ts-node": "^10.9.1",
67
- "tslib": "^2.6.1",
68
- "tsup": "^7.1.0",
69
- "typescript": "^5.1.6"
39
+ "@changesets/cli": "^2.27.11",
40
+ "@types/express": "^4.17.21",
41
+ "@types/greasemonkey": "^4.0.7",
42
+ "@types/node": "^20.17.10",
43
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
44
+ "@typescript-eslint/parser": "^6.21.0",
45
+ "concurrently": "^8.2.2",
46
+ "eslint": "^8.57.1",
47
+ "express": "^4.21.2",
48
+ "ts-node": "^10.9.2",
49
+ "tslib": "^2.8.1",
50
+ "tsup": "^8.3.5",
51
+ "typescript": "^5.7.2"
70
52
  },
71
53
  "files": [
72
54
  "/dist/index.js",
@@ -78,5 +60,23 @@
78
60
  "/README.md",
79
61
  "/CHANGELOG.md",
80
62
  "/LICENSE.txt"
81
- ]
63
+ ],
64
+ "scripts": {
65
+ "lint": "eslint . && tsc --noEmit",
66
+ "build-types": "tsc --emitDeclarationOnly --declaration --outDir dist",
67
+ "build-common": "tsup lib/index.ts --format cjs,esm --clean --treeshake",
68
+ "build-all": "tsup lib/index.ts --format cjs,esm,iife --treeshake --onSuccess \"npm run build-types && npm run post-build-global && echo Finished building.\"",
69
+ "build": "npm run build-common -- && npm run build-types",
70
+ "post-build-global": "npm run node-ts -- ./tools/post-build-global.mts",
71
+ "dev": "npm run build-common -- --sourcemap --watch --onSuccess \"npm run build-types && echo Finished building.\"",
72
+ "dev-all": "npm run build-all -- --watch",
73
+ "update-jsr-version": "npm run node-ts -- ./tools/update-jsr-version.mts",
74
+ "publish-package": "changeset publish",
75
+ "publish-package-jsr": "npm run update-jsr-version && npx jsr publish --allow-dirty",
76
+ "change": "changeset",
77
+ "node-ts": "node --no-warnings=ExperimentalWarning --enable-source-maps --loader ts-node/esm",
78
+ "test-serve": "npm run node-ts -- ./test/TestPage/server.mts",
79
+ "test-dev": "cd test/TestScript && npm run dev",
80
+ "test": "concurrently \"npm run test-serve\" \"npm run test-dev\""
81
+ }
82
82
  }