@@ -13,6 +13,206 @@ import {TemplateResult} from 'lit-html';
1313
1414/* eslint-disable @typescript-eslint/no-explicit-any */
1515
16+ /**
17+ * Configuration parameters for lit-localize when in runtime mode.
18+ */
19+ export interface RuntimeConfiguration {
20+ /**
21+ * Required locale code in which source templates in this project are written,
22+ * and the initial active locale.
23+ */
24+ sourceLocale : string ;
25+
26+ /**
27+ * Required locale codes that are supported by this project. Should not
28+ * include the `sourceLocale` code.
29+ */
30+ targetLocales : Iterable < string > ;
31+
32+ /**
33+ * Required function that returns a promise of the localized templates for the
34+ * given locale code. For security, this function will only ever be called
35+ * with a `locale` that is contained by `targetLocales`.
36+ */
37+ loadLocale : ( locale : string ) => Promise < LocaleModule > ;
38+ }
39+
40+ /**
41+ * Configuration parameters for lit-localize when in transform mode.
42+ */
43+ export interface TransformConfiguration {
44+ /**
45+ * Required locale code in which source templates in this project are written,
46+ * and the active locale.
47+ */
48+ sourceLocale : string ;
49+ }
50+
51+ /**
52+ * The template-like types that can be passed to `msg`.
53+ */
54+ export type TemplateLike =
55+ | string
56+ | TemplateResult
57+ | ( ( ...args : any [ ] ) => string )
58+ | ( ( ...args : any [ ] ) => TemplateResult ) ;
59+
60+ /**
61+ * A mapping from template ID to template.
62+ */
63+ export type TemplateMap = { [ id : string ] : TemplateLike } ;
64+
65+ /**
66+ * The expected exports of a locale module.
67+ */
68+ export interface LocaleModule {
69+ templates : TemplateMap ;
70+ }
71+
72+ class Deferred < T > {
73+ readonly promise : Promise < T > ;
74+ private _resolve ! : ( value : T ) => void ;
75+ private _reject ! : ( error : Error ) => void ;
76+ settled = false ;
77+
78+ constructor ( ) {
79+ this . promise = new Promise < T > ( ( resolve , reject ) => {
80+ this . _resolve = resolve ;
81+ this . _reject = reject ;
82+ } ) ;
83+ }
84+
85+ resolve ( value : T ) {
86+ this . settled = true ;
87+ this . _resolve ( value ) ;
88+ }
89+
90+ reject ( error : Error ) {
91+ this . settled = true ;
92+ this . _reject ( error ) ;
93+ }
94+ }
95+
96+ let activeLocale = '' ;
97+ let loadingLocale : string | undefined ;
98+ let sourceLocale : string | undefined ;
99+ let validLocales : Set < string > | undefined ;
100+ let loadLocale : ( ( locale : string ) => Promise < LocaleModule > ) | undefined ;
101+ let configured = false ;
102+ let templates : TemplateMap | undefined ;
103+ let loading = new Deferred < void > ( ) ;
104+
105+ /**
106+ * Set configuration parameters for lit-localize when in runtime mode. Returns
107+ * an object with functions:
108+ *
109+ * - `getLocale`: Return the active locale code.
110+ * - `setLocale`: Set the active locale code.
111+ *
112+ * Throws if called more than once.
113+ */
114+ export function configureLocalization ( config : RuntimeConfiguration ) {
115+ if ( configured === true ) {
116+ throw new Error ( 'lit-localize can only be configured once' ) ;
117+ }
118+ configured = true ;
119+ activeLocale = sourceLocale = config . sourceLocale ;
120+ validLocales = new Set ( config . targetLocales ) ;
121+ validLocales . add ( config . sourceLocale ) ;
122+ loadLocale = config . loadLocale ;
123+ return { getLocale, setLocale} ;
124+ }
125+
126+ /**
127+ * Set configuration parameters for lit-localize when in transform mode. Returns
128+ * an object with function:
129+ *
130+ * - `getLocale`: Return the active locale code.
131+ *
132+ * Throws if called more than once.
133+ */
134+ export function configureTransformLocalization ( config : TransformConfiguration ) {
135+ if ( configured === true ) {
136+ throw new Error ( 'lit-localize can only be configured once' ) ;
137+ }
138+ configured = true ;
139+ activeLocale = sourceLocale = config . sourceLocale ;
140+ return { getLocale} ;
141+ }
142+
143+ /**
144+ * Return the active locale code.
145+ */
146+ function getLocale ( ) : string {
147+ return activeLocale ;
148+ }
149+
150+ /**
151+ * Set the active locale code, and begin loading templates for that locale using
152+ * the `loadLocale` function that was passed to `configureLocalization`. Returns
153+ * a promise that resolves when the next locale is ready to be rendered.
154+ *
155+ * Note that if a second call to `setLocale` is made while the first requested
156+ * locale is still loading, then the second call takes precedence, and the
157+ * promise returned from the first call will resolve when second locale is
158+ * ready. If you need to know whether a particular locale was loaded, check
159+ * `getLocale` after the promise resolves.
160+ *
161+ * Throws if the given locale is not contained by the configured `sourceLocale`
162+ * or `targetLocales`.
163+ */
164+ function setLocale ( newLocale : string ) : Promise < void > {
165+ if ( ! configured || ! validLocales || ! loadLocale ) {
166+ throw new Error ( 'Must call configureLocalization before setLocale' ) ;
167+ }
168+ if ( newLocale === loadingLocale ?? activeLocale ) {
169+ return loading . promise ;
170+ }
171+ if ( ! validLocales . has ( newLocale ) ) {
172+ throw new Error ( 'Invalid locale code' ) ;
173+ }
174+ loadingLocale = newLocale ;
175+ if ( loading . settled ) {
176+ loading = new Deferred ( ) ;
177+ }
178+ if ( newLocale === sourceLocale ) {
179+ activeLocale = newLocale ;
180+ loadingLocale = undefined ;
181+ templates = undefined ;
182+ loading . resolve ( ) ;
183+ } else {
184+ loadLocale ( newLocale ) . then (
185+ ( mod ) => {
186+ if ( newLocale === loadingLocale ) {
187+ activeLocale = newLocale ;
188+ loadingLocale = undefined ;
189+ templates = mod . templates ;
190+ loading . resolve ( ) ;
191+ }
192+ // Else another locale was requested in the meantime. Don't resolve or
193+ // reject, because the newer load call is going to use the same promise.
194+ // Note the user can call getLocale() after the promise resolves if they
195+ // need to check if the locale is still the one they expected to load.
196+ } ,
197+ ( err ) => {
198+ if ( newLocale === loadingLocale ) {
199+ loading . reject ( err ) ;
200+ }
201+ }
202+ ) ;
203+ }
204+ return loading . promise ;
205+ }
206+
207+ /**
208+ * Make a string or lit-html template localizable.
209+ *
210+ * @param id A project-wide unique identifier for this template.
211+ * @param template A string, a lit-html template, or a function that returns
212+ * either a string or lit-html template.
213+ * @param args In the case that `template` is a function, it is invoked with
214+ * the 3rd and onwards arguments to `msg`.
215+ */
16216export function msg ( id : string , template : string ) : string ;
17217
18218export function msg ( id : string , template : TemplateResult ) : TemplateResult ;
@@ -29,24 +229,19 @@ export function msg<F extends (...args: any[]) => TemplateResult>(
29229 ...params : Parameters < F >
30230) : TemplateResult ;
31231
32- /**
33- * TODO(aomarks) This is a temporary stub implementation of the msg function. It
34- * does not yet support actual localization, and is only used by the "transform"
35- * output mode, since the user needs something to import.
36- *
37- * It may actually make more sense to move most of the generated code from
38- * "runtime" mode into this library, so that users can
39- * `import {msg} from 'lit-localize'` and tell it where to fetch translation
40- * (this will make more sense after the planned revamp to support runtime async
41- * locale loading and re-rendering).
42- */
43232export function msg (
44- _id : string ,
45- template : string | TemplateResult | ( ( ) => string | TemplateResult ) ,
233+ id : string ,
234+ template : TemplateLike ,
46235 ...params : any [ ]
47236) : string | TemplateResult {
237+ if ( templates ) {
238+ const localized = templates [ id ] ;
239+ if ( localized ) {
240+ template = localized ;
241+ }
242+ }
48243 if ( typeof template === 'function' ) {
49- return ( template as any ) ( ...params ) ;
244+ return template ( ...params ) ;
50245 }
51246 return template ;
52247}
0 commit comments