typed-locales 1.0.0 → 1.0.2

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.
@@ -0,0 +1,11 @@
1
+ export type Formatter = (value: string, locale: string) => string;
2
+ declare const formatters: {
3
+ readonly lowercase: (value: string) => string;
4
+ readonly uppercase: (value: string) => string;
5
+ readonly capitalize: (value: string) => string;
6
+ readonly void: () => string;
7
+ readonly weekday: (value: string) => string;
8
+ readonly number: (value: string) => string;
9
+ readonly json: (value: string) => string;
10
+ };
11
+ export default formatters;
@@ -0,0 +1,45 @@
1
+ import baseFormatters from './formatters';
2
+ import type { Formatter } from './formatters';
3
+ type ValueType = null | number | string | undefined | object;
4
+ export type BaseFormatters = keyof typeof baseFormatters;
5
+ export type InternalDeepStringify<T> = {
6
+ [K in keyof T]: T[K] extends string ? string : T[K] extends object ? InternalDeepStringify<T[K]> : never;
7
+ };
8
+ export type DeepStringify<T> = RemoveReadonlyDeep<InternalDeepStringify<T>>;
9
+ export type InternalRemoveReadonlyDeep<T> = {
10
+ -readonly [K in keyof T]: T[K] extends object ? InternalRemoveReadonlyDeep<T[K]> : T[K];
11
+ };
12
+ export type RemoveReadonlyDeep<T> = Simplify<InternalRemoveReadonlyDeep<T>>;
13
+ type PluralSuffix = '_none' | '_one' | '_other';
14
+ export type TranslationType = {
15
+ [key: string]: string | TranslationType;
16
+ };
17
+ type RemovePluralSuffix<T extends string> = T extends `${infer Base}${PluralSuffix}` ? Base : T;
18
+ type PluralKeys<Base extends string> = `${Base}${PluralSuffix}`;
19
+ type DotNestedLeafKeys<T> = {
20
+ [K in keyof T]: K extends string ? T[K] extends Record<string, any> ? `${K}.${DotNestedLeafKeys<T[K]>}` : RemovePluralSuffix<K> : never;
21
+ }[keyof T];
22
+ type GenerateStringFromProperties<T extends Record<string, any>> = T extends Record<string, never> ? string : `${string}{${keyof T & string}${string}` | `${string}{${keyof T & string}|${string}}${string}`;
23
+ export type GenerateTranslationType<T> = {
24
+ -readonly [K in keyof T]: T[K] extends object ? Simplify<GenerateTranslationType<T[K]>> : GenerateStringFromProperties<InterpolationProperties<RemovePluralSuffix<T[K] & string>, IsPlural<T, RemovePluralSuffix<K & string>>>>;
25
+ };
26
+ type ExtractPlaceholders<T extends string> = T extends `${infer _Start}{${infer Placeholder}}${infer Rest}` ? (Placeholder extends `${infer Name}|${infer _Formatters}` ? Name : Placeholder) | ExtractPlaceholders<Rest> : never;
27
+ type HasPluralKeys<T, Path extends string> = Path extends `${infer K}.${infer Rest}` ? K extends keyof T ? HasPluralKeys<T[K], Rest> : false : PluralKeys<Path> & keyof T extends never ? false : true;
28
+ type IsPlural<T, Path extends string> = HasPluralKeys<T, Path>;
29
+ type GetValue<T, Path extends string> = Path extends `${infer K}.${infer Rest}` ? K extends keyof T ? GetValue<T[K], Rest> : never : Path extends keyof T ? T[Path] : T[PluralKeys<Path> & keyof T];
30
+ type InterpolationProperties<S extends string, IsPlural extends boolean> = IsPlural extends true ? {
31
+ count: number;
32
+ } & {
33
+ [K in Exclude<ExtractPlaceholders<S>, 'count'>]: ValueType;
34
+ } : ExtractPlaceholders<S> extends never ? {} : {
35
+ [K in ExtractPlaceholders<S>]: ValueType;
36
+ };
37
+ type InternalSimplify<T> = {
38
+ [K in keyof T]: InternalSimplify<T[K]>;
39
+ } & {};
40
+ type DeepOmitNever<T> = {
41
+ [K in keyof T as T[K] extends never ? never : T[K] extends Record<string, any> ? DeepOmitNever<T[K]> extends never ? never : K : K]: T[K] extends Record<string, any> ? DeepOmitNever<T[K]> : T[K];
42
+ } extends infer U ? keyof U extends never ? never : U : never;
43
+ export type Simplify<T> = InternalSimplify<DeepOmitNever<T>>;
44
+ export declare const getTranslate: <Translations, ExtraFormattersType extends string = string, ExtraFormatters extends Record<ExtraFormattersType, Formatter> = Record<ExtraFormattersType, Formatter>>(translations: Translations, locale: string, extraFormatters?: ExtraFormatters) => <Key extends DotNestedLeafKeys<Translations>>(key: Key, ...arguments_: InterpolationProperties<GetValue<Translations, Key> & string, IsPlural<Translations, Key>> extends Record<string, never> ? [] : [params: Simplify<InterpolationProperties<GetValue<Translations, Key> & string, IsPlural<Translations, Key>>>]) => GetValue<Translations, Key>;
45
+ export {};
package/dist/test.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ declare const en: {
2
+ readonly test: "Regular translation";
3
+ readonly nested: {
4
+ readonly test: "Nested {translation|myCustomFormatter}";
5
+ readonly deep: {
6
+ readonly again: "Nested again with {value} and {otherValue}";
7
+ };
8
+ };
9
+ readonly withValue: "With value {value}";
10
+ readonly multipleValues: "Multiple values: {one}, {two}, and {three}";
11
+ readonly examplePlural_none: "No items available";
12
+ readonly examplePlural_one: "One item available";
13
+ readonly examplePlural_other: "{count} items available";
14
+ readonly examplePluralWithOtherValues_none: "No items for {user}";
15
+ readonly examplePluralWithOtherValues_one: "One item for {user}";
16
+ readonly examplePluralWithOtherValues_other: "{count} items for {user} and {otherUser}";
17
+ readonly exampleWithFormatting: "Formatted {value|uppercase} text and {other|lowercase}";
18
+ readonly exampleWithJSONFormatter: "JSON formatter: {data|json}";
19
+ readonly pluralWithNestedSubstitution_none: "No results found for {query}";
20
+ readonly pluralWithNestedSubstitution_one: "One result for {query} with {user|capitalize}";
21
+ readonly pluralWithNestedSubstitution_other: "{count} results for {query} by {user|capitalize}";
22
+ readonly mixedPluralNested_none: "No {itemType} in {location}";
23
+ readonly mixedPluralNested_one: "One {itemType} in {location|uppercase}";
24
+ readonly mixedPluralNested_other: "{count} {itemType}s in {location|uppercase}";
25
+ readonly onlyFormat: "Just formatting: {value|capitalize}";
26
+ readonly escapeBraces: "Braces like this: \\{notAKey\\}";
27
+ };
28
+ export default en;
@@ -0,0 +1,28 @@
1
+ declare const es: {
2
+ readonly test: "Traducción regular";
3
+ readonly nested: {
4
+ readonly test: "Anidado {translation|myCustomFormatter}";
5
+ readonly deep: {
6
+ readonly again: "Anidado nuevamente con {value} y {otherValue}";
7
+ };
8
+ };
9
+ readonly withValue: "Con valor {value}";
10
+ readonly multipleValues: "Múltiples valores: {one}, {two} y {three}";
11
+ readonly examplePlural_none: "No hay elementos disponibles";
12
+ readonly examplePlural_one: "Un elemento disponible";
13
+ readonly examplePlural_other: "{count} elementos disponibles";
14
+ readonly examplePluralWithOtherValues_none: "No hay elementos para {user}";
15
+ readonly examplePluralWithOtherValues_one: "Un elemento para {user}";
16
+ readonly examplePluralWithOtherValues_other: "{count} elementos para {user} y {otherUser}";
17
+ readonly exampleWithFormatting: "Texto formateado {value|uppercase} y {other|lowercase}";
18
+ readonly exampleWithJSONFormatter: "Formateador JSON: {data|json}";
19
+ readonly pluralWithNestedSubstitution_none: "No se encontraron resultados para {query}";
20
+ readonly pluralWithNestedSubstitution_one: "Un resultado para {query} con {user|capitalize}";
21
+ readonly pluralWithNestedSubstitution_other: "{count} resultados para {query} por {user|capitalize}";
22
+ readonly mixedPluralNested_none: "No hay {itemType} en {location}";
23
+ readonly mixedPluralNested_one: "Un {itemType} en {location|uppercase}";
24
+ readonly mixedPluralNested_other: "{count} {itemType}s en {location|uppercase}";
25
+ readonly onlyFormat: "Solo formateo: {value|capitalize}";
26
+ readonly escapeBraces: "Llaves como estas: \\{notAKey\\}";
27
+ };
28
+ export default es;
@@ -0,0 +1,13 @@
1
+ import type { BaseFormatters, RemoveReadonlyDeep } from ".";
2
+ type ErrorMessage<Value extends string, T extends string> = `You are using an invalid formatter: ${T} in: "${Value}"`;
3
+ type ExtractFormatter<T extends string> = T extends `${string}{${string}|${infer F}}${string}` ? F : never;
4
+ type CountOpenBraces<T extends string, Count extends readonly unknown[] = []> = T extends `${infer First}${infer Rest}` ? First extends '{' ? CountOpenBraces<Rest, [...Count, unknown]> : CountOpenBraces<Rest, Count> : Count['length'];
5
+ type CountCloseBraces<T extends string, Count extends readonly unknown[] = []> = T extends `${infer First}${infer Rest}` ? First extends '}' ? CountCloseBraces<Rest, [...Count, unknown]> : CountCloseBraces<Rest, Count> : Count['length'];
6
+ type BalancedBraces<T extends string> = CountOpenBraces<T> extends CountCloseBraces<T> ? never : `Brackets are not balanced in: "${T}"`;
7
+ type ValidateFormatter<T extends string, Formatters extends string> = ExtractFormatter<T> extends never ? BalancedBraces<T> : ExtractFormatter<T> extends Formatters ? BalancedBraces<T> : ErrorMessage<T, ExtractFormatter<T>>;
8
+ type InternalValidateTranslation<T, Formatters extends string, KeyPath extends string = ''> = T extends Record<string, any> ? {
9
+ [K in keyof T]: InternalValidateTranslation<T[K], Formatters, KeyPath extends '' ? K & string : `${KeyPath}.${K & string}`>;
10
+ } : T extends string ? ValidateFormatter<T, Formatters> : T;
11
+ export type ValidateTranslation<T, Formatters extends string> = RemoveReadonlyDeep<InternalValidateTranslation<T, BaseFormatters | Formatters>>;
12
+ export type EnsureValidTranslation<T extends never> = T;
13
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typed-locales",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Type safe utilities for translating strings",
5
5
  "main": "index.js",
6
6
  "type": "module",