@@ -31,8 +31,17 @@ interface TranslationVariableReplacementObject<T> {
3131 escape : boolean
3232}
3333
34+ /**
35+ * Extracts variables from a translation key
36+ * @notExported
37+ */
38+ type ExtractedVariables < T extends string > =
39+ T extends `${string } {${infer Variable } }${infer Rest } `
40+ ? Variable | ExtractedVariables < Rest >
41+ : never
42+
3443/** @notExported */
35- type TranslationVariables = Record < string , string | number | TranslationVariableReplacementObject < string | number > >
44+ type TranslationVariables < K extends string > = Record < ExtractedVariables < K > , string | number | TranslationVariableReplacementObject < string | number > >
3645
3746/**
3847 * Translate a string
@@ -47,10 +56,10 @@ type TranslationVariables = Record<string, string | number | TranslationVariable
4756 *
4857 * @return {string }
4958 */
50- export function translate (
59+ export function translate < T extends string > (
5160 app : string ,
52- text : string ,
53- vars ?: TranslationVariables ,
61+ text : T ,
62+ vars ?: TranslationVariables < T > ,
5463 number ?: number ,
5564 options ?: TranslationOptions ,
5665) : string {
@@ -71,8 +80,8 @@ export function translate(
7180 // TODO: cache this function to avoid inline recreation
7281 // of the same function over and over again in case
7382 // translate() is used in a loop
74- const _build = ( text : string , vars ?: TranslationVariables , number ?: number ) => {
75- return text . replace ( / % n / g, '' + number ) . replace ( / { ( [ ^ { } ] * ) } / g, ( match , key ) => {
83+ const _build = ( text : string , vars ?: TranslationVariables < T > , number ?: number ) => {
84+ return text . replace ( / % n / g, '' + number ) . replace ( / { ( [ ^ { } ] * ) } / g, ( match , key : ExtractedVariables < T > ) => {
7685 if ( vars === undefined || ! ( key in vars ) ) {
7786 return optEscape ( match )
7887 }
@@ -119,12 +128,12 @@ export function translate(
119128 * @param {object } vars of placeholder key to value
120129 * @param {object } options options object
121130 */
122- export function translatePlural (
131+ export function translatePlural < T extends string , K extends string , > (
123132 app : string ,
124- textSingular : string ,
125- textPlural : string ,
133+ textSingular : T ,
134+ textPlural : K ,
126135 number : number ,
127- vars ?: Record < string , string | number > ,
136+ vars ?: TranslationVariables < T > & TranslationVariables < K > ,
128137 options ?: TranslationOptions ,
129138) : string {
130139 const identifier = '_' + textSingular + '_::_' + textPlural + '_'
@@ -139,10 +148,11 @@ export function translatePlural(
139148 }
140149 }
141150
151+ // vars type is casted to allow extra keys without runtime filtering (they are harmless), and without allowing wrong keys in translate
142152 if ( number === 1 ) {
143- return translate ( app , textSingular , vars , number , options )
153+ return translate ( app , textSingular , vars as TranslationVariables < T > , number , options )
144154 } else {
145- return translate ( app , textPlural , vars , number , options )
155+ return translate ( app , textPlural , vars as TranslationVariables < K > , number , options )
146156 }
147157}
148158
0 commit comments