Skip to content

Commit ee571e5

Browse files
committed
feat: dynamic locale switching
1 parent 745ea5b commit ee571e5

File tree

6 files changed

+49
-38
lines changed

6 files changed

+49
-38
lines changed

apps/mmstack/src/app/app.routes.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
11
import { Routes } from '@angular/router';
2+
import { resolveTestTranslation } from './locale/test.register';
23

3-
export const routes: Routes = [];
4+
export const routes: Routes = [
5+
{
6+
path: '',
7+
resolve: {
8+
resolveTestTranslation,
9+
},
10+
loadComponent: () =>
11+
import('./demo.component').then((m) => m.DemoComponent),
12+
},
13+
];

apps/mmstack/src/app/app.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
import { Component } from '@angular/core';
2-
import {
3-
createSelectState,
4-
SelectField,
5-
SelectOptionContent,
6-
} from '@mmstack/form-material';
2+
import { RouterOutlet } from '@angular/router';
3+
import { createSelectState } from '@mmstack/form-material';
74

85
@Component({
96
selector: 'app-root',
10-
imports: [SelectField, SelectOptionContent],
11-
template: `
12-
<mm-select-field [state]="demo">
13-
<div *mmSelectOptionContent="let opt">{{ opt.label() }} zaz</div>
14-
</mm-select-field>
15-
`,
7+
imports: [RouterOutlet],
8+
template: ` <router-outlet /> `,
169
styles: ``,
1710
})
1811
export class App {

packages/translate/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,12 @@ export default createQuoteTranslation('sl-SI', {
174174
Use registerNamespace to prepare your namespace definition and obtain the injectNamespaceT function and the resolveNamespaceTranslation resolver function. Use the resolver in your Angular routes.
175175

176176
```typescript
177-
import q from './quote.namespace';
177+
import { registerNamespace } from '@mmstack/translate';
178178

179179
// Register the namespace
180180
// Example: packages/quote/src/lib/quote.t.ts
181181
const r = registerNamespace(
182+
// you can also fetch from a server if you prefer, as long as the shape is the same
182183
() => import('./quote.namespace').then((m) => m.default), // Default locale's compiled translation (functions as fallback if no locale of type provided)
183184
{
184185
// Map other locales to promise factories (dynamic imports)

packages/translate/src/lib/translate.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
2+
afterRenderEffect,
23
computed,
34
Directive,
4-
effect,
55
ElementRef,
66
inject,
77
input,
@@ -60,8 +60,10 @@ export abstract class Translate<
6060
const renderer = inject(Renderer2);
6161
const el = inject<ElementRef<HTMLElement>>(ElementRef);
6262

63-
effect(() =>
64-
renderer.setProperty(el.nativeElement, 'textContent', translation()),
65-
);
63+
afterRenderEffect({
64+
write: () => {
65+
renderer.setProperty(el.nativeElement, 'textContent', translation());
66+
},
67+
});
6668
}
6769
}

packages/translate/src/lib/translation-store.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export function injectDefaultLocale() {
4545
export class TranslationStore {
4646
private readonly cache = createIntlCache();
4747
private readonly config = injectIntlConfig();
48+
readonly loadQueue = signal<string[]>([]);
4849
readonly locale = signal(inject(LOCALE_ID));
4950
private readonly defaultLocale = injectDefaultLocale();
5051
private readonly translations = signal<
@@ -73,18 +74,8 @@ export class TranslationStore {
7374
{},
7475
);
7576

76-
private isInit = true;
77-
private readonly skipInitLocale = computed(() => {
78-
// should load new loaders if switched back to initial locale, so we do it like this
79-
if (this.isInit) {
80-
this.isInit = false;
81-
return null;
82-
}
83-
return this.locale();
84-
});
85-
8677
readonly dynamicLocaleLoader = resource({
87-
params: () => this.skipInitLocale(),
78+
params: computed(() => this.loadQueue().at(0) ?? null),
8879
loader: async ({ params: newLocale, abortSignal }) => {
8980
if (!newLocale) return;
9081

@@ -159,12 +150,17 @@ export class TranslationStore {
159150
)
160151
return;
161152
const dynamicLocales = this.dynamicLocaleLoader.value();
153+
162154
if (!dynamicLocales) return;
163155
for (const locale of dynamicLocales.locales) {
164156
this.register(locale.namespace, {
165157
[dynamicLocales.locale]: locale.flat,
166158
});
167159
}
160+
this.loadQueue.update((q) =>
161+
q.filter((l) => l !== dynamicLocales.locale),
162+
);
163+
this.locale.set(dynamicLocales.locale);
168164
});
169165
}
170166

@@ -250,22 +246,25 @@ export function injectDynamicLocale(): WritableSignal<string> & {
250246
} {
251247
const store = inject(TranslationStore);
252248

253-
const source = store.locale as WritableSignal<string> & {
249+
const source = computed(() => store.locale()) as WritableSignal<string> & {
254250
isLoading: Signal<boolean>;
255251
};
256252

257-
const originalSet = source.set;
258-
259-
source.set = (value: string) => {
260-
if (value === untracked(source) || !store.hasLocaleLoaders(value)) return;
261-
262-
originalSet(value);
253+
const set = (value: string) => {
254+
if (
255+
value === untracked(source) ||
256+
!store.hasLocaleLoaders(value) ||
257+
untracked(store.loadQueue).includes(value)
258+
)
259+
return;
260+
store.loadQueue.update((q) => [...q, value]);
263261
};
264-
262+
source.set = set;
265263
source.update = (updater: (value: string) => string) => {
266264
const next = updater(untracked(source));
267265
source.set(next);
268266
};
267+
source.asReadonly = () => source;
269268

270269
source.isLoading = store.dynamicLocaleLoader.isLoading;
271270

packages/translate/src/lib/translator.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ export abstract class Translator<
2222

2323
transform<K extends keyof TMap & string>(
2424
key: K,
25-
...args: TMap[K] extends void ? [] : [TMap[K]]
25+
...args: TMap[K] extends void
26+
? [locale?: string]
27+
: [TMap[K], locale?: string]
2628
): string {
27-
return this.t(key, ...args);
29+
const actualArgs = args.filter(
30+
(a) => typeof a === 'object',
31+
) as TMap[K] extends void ? [] : [TMap[K]];
32+
33+
return this.t(key, ...actualArgs);
2834
}
2935
}

0 commit comments

Comments
 (0)