feat(project-editor): add Languages tab with generic locale sync
This commit is contained in:
119
src/app/features/project-editor/services/locale-sync.service.ts
Normal file
119
src/app/features/project-editor/services/locale-sync.service.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BootstrapConfig, FooterNavigationGroupConfig, NavigationItemConfig, NavigationLocalizedText } from '../../../shared/models/config';
|
||||
|
||||
type SyncMode = 'add' | 'remove';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LocaleSyncService {
|
||||
addLocale(bootstrap: BootstrapConfig, locale: string): BootstrapConfig {
|
||||
return this.sync(bootstrap, locale, 'add');
|
||||
}
|
||||
|
||||
removeLocale(bootstrap: BootstrapConfig, locale: string): BootstrapConfig {
|
||||
if (locale === bootstrap.localization.defaultLocale) {
|
||||
return bootstrap;
|
||||
}
|
||||
return this.sync(bootstrap, locale, 'remove');
|
||||
}
|
||||
|
||||
private sync(bootstrap: BootstrapConfig, rawLocale: string, mode: SyncMode): BootstrapConfig {
|
||||
const locale = rawLocale.trim().toLowerCase();
|
||||
if (!locale) {
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
const alreadyPresent = bootstrap.localization.supportedLocales.includes(locale);
|
||||
if (mode === 'add' && alreadyPresent) {
|
||||
return bootstrap;
|
||||
}
|
||||
if (mode === 'remove' && !alreadyPresent) {
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
const supportedLocales = mode === 'add'
|
||||
? [...bootstrap.localization.supportedLocales, locale]
|
||||
: bootstrap.localization.supportedLocales.filter(existing => existing !== locale);
|
||||
|
||||
return {
|
||||
...bootstrap,
|
||||
tenant: { ...bootstrap.tenant, supportedLocales },
|
||||
localization: { ...bootstrap.localization, supportedLocales },
|
||||
staticPages: this.syncStaticPages(bootstrap.staticPages, locale, mode),
|
||||
navigation: {
|
||||
...bootstrap.navigation,
|
||||
header: this.syncNavItems(bootstrap.navigation.header, locale, mode),
|
||||
footer: this.syncFooterNav(bootstrap.navigation.footer, locale, mode),
|
||||
sidebar: bootstrap.navigation.sidebar ? this.syncNavItems(bootstrap.navigation.sidebar, locale, mode) : bootstrap.navigation.sidebar,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private syncStaticPages(staticPages: BootstrapConfig['staticPages'], locale: string, mode: SyncMode): BootstrapConfig['staticPages'] {
|
||||
if (!staticPages || Array.isArray(staticPages)) {
|
||||
return staticPages;
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(staticPages).map(([id, page]) => [id, {
|
||||
...page,
|
||||
translations: this.syncRecord(page.translations, locale, mode, () => ({ title: '', html: '' })),
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
private syncNavItems(items: NavigationItemConfig[], locale: string, mode: SyncMode): NavigationItemConfig[] {
|
||||
return items.map(item => ({
|
||||
...item,
|
||||
label: this.syncLabel(item.label, locale, mode),
|
||||
children: item.children ? this.syncNavItems(item.children, locale, mode) : item.children,
|
||||
}));
|
||||
}
|
||||
|
||||
private syncFooterNav(
|
||||
footer: NavigationItemConfig[] | FooterNavigationGroupConfig[],
|
||||
locale: string,
|
||||
mode: SyncMode
|
||||
): NavigationItemConfig[] | FooterNavigationGroupConfig[] {
|
||||
if (footer.length === 0) {
|
||||
return footer;
|
||||
}
|
||||
|
||||
if ('items' in footer[0]) {
|
||||
return (footer as FooterNavigationGroupConfig[]).map(group => ({
|
||||
...group,
|
||||
groupTitle: this.syncLabel(group.groupTitle, locale, mode),
|
||||
items: group.items.map(item => ({ ...item, label: this.syncLabel(item.label, locale, mode) })),
|
||||
}));
|
||||
}
|
||||
|
||||
return this.syncNavItems(footer as NavigationItemConfig[], locale, mode);
|
||||
}
|
||||
|
||||
private syncLabel(
|
||||
label: string | NavigationLocalizedText | undefined,
|
||||
locale: string,
|
||||
mode: SyncMode
|
||||
): string | NavigationLocalizedText | undefined {
|
||||
if (label === undefined || typeof label === 'string') {
|
||||
return label;
|
||||
}
|
||||
return this.syncRecord(label, locale, mode, () => Object.values(label)[0] ?? '');
|
||||
}
|
||||
|
||||
private syncRecord<T>(
|
||||
record: Record<string, T> | undefined,
|
||||
locale: string,
|
||||
mode: SyncMode,
|
||||
createEmpty: () => T
|
||||
): Record<string, T> {
|
||||
const next: Record<string, T> = { ...(record ?? {}) };
|
||||
if (mode === 'add') {
|
||||
if (!(locale in next)) {
|
||||
next[locale] = createEmpty();
|
||||
}
|
||||
} else {
|
||||
delete next[locale];
|
||||
}
|
||||
return next;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user