meta object

This commit is contained in:
Lone Iversen
2023-09-05 12:14:37 +02:00
parent 6a406c9ad5
commit b092738fa5
11 changed files with 64 additions and 65 deletions

View File

@@ -34,8 +34,7 @@ The frontend has an API formatter that takes the OpenAPI schema file and convert
### Caveats
1. There is currently no way to add translations. All texts in the UI are expected to be written in Umbracos default language of English.
2. The backoffice can be run and tested against a real Umbraco instance by cloning down the `v14/dev` branch, but there are no guarantees about how well it works yet.
1. The backoffice can be run and tested against a real Umbraco instance by cloning down the `v14/dev` branch, but there are no guarantees about how well it works yet.
**Current schema for API:**

View File

@@ -25,7 +25,7 @@ import '../src/libs/controller-api/controller-host-initializer.element.ts';
import '../src/packages/core/components';
import { manifests as documentManifests } from '../src/packages/documents';
import { manifests as translationManifests } from '../src/packages/core/localization/manifests';
import { manifests as localizationManifests } from '../src/packages/core/localization/manifests';
// MSW
startMockServiceWorker({ serviceWorker: { url: (import.meta.env.VITE_BASE_PATH ?? '/') + 'mockServiceWorker.js' } });
@@ -39,7 +39,7 @@ class UmbStoryBookElement extends UmbLitElement {
this._registerExtensions(documentManifests);
this.provideContext(UMB_MODAL_CONTEXT_TOKEN, new UmbModalManagerContext(this));
this._registerExtensions(translationManifests);
this._registerExtensions(localizationManifests);
umbLocalizationRegistry.loadLanguage('en-us'); // register default language
}

View File

@@ -1,5 +1,5 @@
import { aTimeout, elementUpdated, expect, fixture, html } from '@open-wc/testing';
import { DefaultTranslationSet, TranslationSet, registerTranslation, translations } from './manager.js';
import { DefaultLocalizationSet, LocalizationSet, registerLocalization, localizations } from './manager.js';
import { UmbLocalizeController } from './localize.controller.js';
import { LitElement, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
@@ -10,7 +10,7 @@ class UmbLocalizeControllerHostElement extends UmbElementMixin(LitElement) {
@property() lang = 'en-us';
}
interface TestTranslation extends TranslationSet {
interface TestLocalization extends LocalizationSet {
close: string;
logout: string;
withInlineToken: any;
@@ -19,8 +19,8 @@ interface TestTranslation extends TranslationSet {
numUsersSelected: (count: number) => string;
}
//#region Translations
const english: TestTranslation = {
//#region Localizations
const english: TestLocalization = {
$code: 'en-us',
$dir: 'ltr',
close: 'Close',
@@ -35,20 +35,20 @@ const english: TestTranslation = {
},
};
const englishOverride: DefaultTranslationSet = {
const englishOverride: DefaultLocalizationSet = {
$code: 'en-us',
$dir: 'ltr',
close: 'Close 2',
};
const danish: DefaultTranslationSet = {
const danish: DefaultLocalizationSet = {
$code: 'da',
$dir: 'ltr',
close: 'Luk',
notOnRegional: 'Not on regional',
};
const danishRegional: DefaultTranslationSet = {
const danishRegional: DefaultLocalizationSet = {
$code: 'da-dk',
$dir: 'ltr',
close: 'Luk',
@@ -56,10 +56,10 @@ const danishRegional: DefaultTranslationSet = {
//#endregion
describe('UmbLocalizeController', () => {
let controller: UmbLocalizeController<TestTranslation>;
let controller: UmbLocalizeController<TestLocalization>;
beforeEach(async () => {
registerTranslation(english, danish, danishRegional);
registerLocalization(english, danish, danishRegional);
document.documentElement.lang = english.$code;
document.documentElement.dir = english.$dir;
await aTimeout(0);
@@ -76,7 +76,7 @@ describe('UmbLocalizeController', () => {
afterEach(() => {
controller.destroy();
translations.clear();
localizations.clear();
});
it('should have a default language', () => {
@@ -131,7 +131,7 @@ describe('UmbLocalizeController', () => {
it('should override a term if new translation is registered', () => {
// Let the registry load the new extension
registerTranslation(englishOverride);
registerLocalization(englishOverride);
expect(controller.term('close')).to.equal('Close 2');
});
@@ -174,7 +174,7 @@ describe('UmbLocalizeController', () => {
it('should return a date with a custom format', () => {
expect(controller.date(new Date(2020, 11, 31), { month: 'long', day: '2-digit', year: 'numeric' })).to.equal(
'December 31, 2020'
'December 31, 2020',
);
});
});
@@ -198,7 +198,7 @@ describe('UmbLocalizeController', () => {
it('should return a number with a custom format', () => {
expect(controller.number(123456.789, { minimumFractionDigits: 2, maximumFractionDigits: 2 })).to.equal(
'123,456.79'
'123,456.79',
);
});
});

View File

@@ -12,14 +12,14 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import {
DefaultTranslationSet,
DefaultLocalizationSet,
FunctionParams,
TranslationSet,
LocalizationSet,
connectedElements,
documentDirection,
documentLanguage,
fallback,
translations,
localizations,
} from './manager.js';
import { UmbController, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
@@ -42,7 +42,7 @@ const LocalizeControllerAlias = Symbol();
* }
* ```
*/
export class UmbLocalizeController<TranslationType extends TranslationSet = DefaultTranslationSet>
export class UmbLocalizeController<LocalizationType extends LocalizationSet = DefaultLocalizationSet>
implements UmbController
{
#host;
@@ -88,19 +88,19 @@ export class UmbLocalizeController<TranslationType extends TranslationSet = Defa
return `${this.#hostEl.lang || documentLanguage}`.toLowerCase();
}
private getTranslationData(lang: string) {
private getLocalizationData(lang: string) {
const locale = new Intl.Locale(lang);
const language = locale?.language.toLowerCase();
const region = locale?.region?.toLowerCase() ?? '';
const primary = <TranslationType>translations.get(`${language}-${region}`);
const secondary = <TranslationType>translations.get(language);
const primary = <LocalizationType>localizations.get(`${language}-${region}`);
const secondary = <LocalizationType>localizations.get(language);
return { locale, language, region, primary, secondary };
}
/** Outputs a translated term. */
term<K extends keyof TranslationType>(key: K, ...args: FunctionParams<TranslationType[K]>): string {
const { primary, secondary } = this.getTranslationData(this.lang());
term<K extends keyof LocalizationType>(key: K, ...args: FunctionParams<LocalizationType[K]>): string {
const { primary, secondary } = this.getLocalizationData(this.lang());
let term: any;
// Look for a matching term using regionCode, code, then the fallback
@@ -108,8 +108,8 @@ export class UmbLocalizeController<TranslationType extends TranslationSet = Defa
term = primary[key];
} else if (secondary && secondary[key]) {
term = secondary[key];
} else if (fallback && fallback[key as keyof TranslationSet]) {
term = fallback[key as keyof TranslationSet];
} else if (fallback && fallback[key as keyof LocalizationSet]) {
term = fallback[key as keyof LocalizationSet];
} else {
return String(key);
}

View File

@@ -16,21 +16,21 @@ import type { LitElement } from '@umbraco-cms/backoffice/external/lit';
export type FunctionParams<T> = T extends (...args: infer U) => string ? U : [];
export interface TranslationSet {
export interface LocalizationSet {
$code: string; // e.g. en, en-GB
$dir: 'ltr' | 'rtl';
}
export interface DefaultTranslationSet extends TranslationSet {
export interface DefaultLocalizationSet extends LocalizationSet {
[key: string]: UmbLocalizationEntry;
}
export const connectedElements = new Set<HTMLElement>();
const documentElementObserver = new MutationObserver(update);
export const translations: Map<string, TranslationSet> = new Map();
export const localizations: Map<string, LocalizationSet> = new Map();
export let documentDirection = document.documentElement.dir || 'ltr';
export let documentLanguage = document.documentElement.lang || navigator.language;
export let fallback: TranslationSet;
export let fallback: LocalizationSet;
// Watch for changes on <html lang>
documentElementObserver.observe(document.documentElement, {
@@ -39,15 +39,15 @@ documentElementObserver.observe(document.documentElement, {
});
/** Registers one or more translations */
export function registerTranslation(...translation: TranslationSet[]) {
export function registerLocalization(...translation: LocalizationSet[]) {
translation.map((t) => {
const code = t.$code.toLowerCase();
if (translations.has(code)) {
if (localizations.has(code)) {
// Merge translations that share the same language code
translations.set(code, { ...translations.get(code), ...t });
localizations.set(code, { ...localizations.get(code), ...t });
} else {
translations.set(code, t);
localizations.set(code, t);
}
// The first translation that's registered is the fallback

View File

@@ -20,7 +20,7 @@ export interface MetaLocalization {
culture: string;
/**
* @summary The direction of the translations (left-to-right or right-to-left).
* @summary The direction of the localizations (left-to-right or right-to-left).
* @description
* The value is used to describe the direction of the translations according to the extension system
* and it will be set as the `dir` attribute on the `<html>` element. It defaults to `ltr`.
@@ -31,7 +31,7 @@ export interface MetaLocalization {
direction?: 'ltr' | 'rtl';
/**
* The translations.
* The localizations.
* @example
* {
* "general": {
@@ -40,5 +40,5 @@ export interface MetaLocalization {
* }
* }
*/
translations?: UmbLocalizationDictionary;
localizations?: UmbLocalizationDictionary;
}

View File

@@ -10,7 +10,7 @@ const english = {
name: 'Test English',
meta: {
culture: 'en',
translations: {
localizations: {
general: {
close: 'Close',
logout: 'Log out',
@@ -33,7 +33,7 @@ const danish = {
name: 'Test Danish',
meta: {
culture: 'da',
translations: {
localizations: {
general: {
close: 'Luk',
},

View File

@@ -10,7 +10,7 @@ const english: ManifestLocalization = {
meta: {
culture: 'en-us',
direction: 'ltr',
translations: {
localizations: {
general: {
close: 'Close',
logout: 'Log out',
@@ -32,7 +32,7 @@ const englishOverride: ManifestLocalization = {
name: 'Test English',
meta: {
culture: 'en-us',
translations: {
localizations: {
general: {
close: 'Close 2',
},
@@ -46,7 +46,7 @@ const danish: ManifestLocalization = {
name: 'Test Danish',
meta: {
culture: 'da',
translations: {
localizations: {
general: {
close: 'Luk',
notOnRegional: 'Not on regional',
@@ -61,7 +61,7 @@ const danishRegional: ManifestLocalization = {
name: 'Test Danish (Denmark)',
meta: {
culture: 'da-dk',
translations: {
localizations: {
general: {
close: 'Luk',
},
@@ -84,7 +84,7 @@ describe('UmbLocalizeController', () => {
});
afterEach(() => {
registry.translations.clear();
registry.localizations.clear();
});
it('should set the document language and direction', async () => {
@@ -93,9 +93,9 @@ describe('UmbLocalizeController', () => {
});
it('should load translations for the current language', async () => {
expect(registry.translations.has(english.meta.culture)).to.be.true;
expect(registry.localizations.has(english.meta.culture)).to.be.true;
const current = registry.translations.get(english.meta.culture);
const current = registry.localizations.get(english.meta.culture);
expect(current).to.have.property('general_close', 'Close'); // Also tests that the translation is flattened.
expect(current).to.have.property('general_logout', 'Log out');
});
@@ -105,7 +105,7 @@ describe('UmbLocalizeController', () => {
await aTimeout(0);
const current = registry.translations.get(english.meta.culture);
const current = registry.localizations.get(english.meta.culture);
expect(current).to.have.property('general_close', 'Close 2');
expect(current).to.have.property('general_logout', 'Log out');
});
@@ -116,10 +116,10 @@ describe('UmbLocalizeController', () => {
await aTimeout(0);
// Check that the new language is loaded.
expect(registry.translations.has(danish.meta.culture)).to.be.true;
expect(registry.localizations.has(danish.meta.culture)).to.be.true;
// Check that the new language has the correct translations.
const current = registry.translations.get(danish.meta.culture);
const current = registry.localizations.get(danish.meta.culture);
expect(current).to.have.property('general_close', 'Luk');
});
@@ -129,7 +129,7 @@ describe('UmbLocalizeController', () => {
await aTimeout(0);
// Check that both the regional and the base language is loaded.
expect(registry.translations.has(danishRegional.meta.culture), 'expected "da-dk" to be present').to.be.true;
expect(registry.translations.has(danish.meta.culture), 'expected "da" to be present').to.be.true;
expect(registry.localizations.has(danishRegional.meta.culture), 'expected "da-dk" to be present').to.be.true;
expect(registry.localizations.has(danish.meta.culture), 'expected "da" to be present').to.be.true;
});
});

View File

@@ -1,9 +1,9 @@
import {
UmbLocalizationDictionary,
UmbLocalizationFlatDictionary,
TranslationSet,
registerTranslation,
translations,
LocalizationSet,
registerLocalization,
localizations,
} from '@umbraco-cms/backoffice/localization-api';
import { hasDefaultExport, loadExtension } from '@umbraco-cms/backoffice/extension-api';
import { UmbBackofficeExtensionRegistry, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
@@ -21,8 +21,8 @@ export class UmbLocalizationRegistry {
/**
* Get the current registered translations.
*/
get translations() {
return translations;
get localizations() {
return localizations;
}
get isDefaultLoaded() {
@@ -57,8 +57,8 @@ export class UmbLocalizationRegistry {
const innerDictionary: UmbLocalizationFlatDictionary = {};
// If extension contains a dictionary, add it to the inner dictionary.
if (extension.meta.translations) {
for (const [dictionaryName, dictionary] of Object.entries(extension.meta.translations)) {
if (extension.meta.localizations) {
for (const [dictionaryName, dictionary] of Object.entries(extension.meta.localizations)) {
this.#addOrUpdateDictionary(innerDictionary, dictionaryName, dictionary);
}
}
@@ -77,12 +77,12 @@ export class UmbLocalizationRegistry {
$code: extension.meta.culture.toLowerCase(),
$dir: extension.meta.direction ?? 'ltr',
...innerDictionary,
} satisfies TranslationSet;
} satisfies LocalizationSet;
}),
);
if (translations.length) {
registerTranslation(...translations);
registerLocalization(...translations);
// Set the document language
const newLang = locale.baseName.toLowerCase();

View File

@@ -34,10 +34,10 @@ const menuSectionSidebarApp: ManifestTypes = {
const dashboards: Array<ManifestDashboard> = [
{
type: 'dashboard',
alias: 'Umb.Dashboard.TranslationDictionary',
name: 'Dictionary Translation Dashboard',
alias: 'Umb.Dashboard.LocalizationDictionary',
name: 'Dictionary localization Dashboard',
elementName: 'umb-dashboard-translation-dictionary',
loader: () => import('./dashboards/dictionary/dashboard-translation-dictionary.element.js'),
loader: () => import('./dashboards/dictionary/dashboard-localization-dictionary.element.js'),
meta: {
label: 'Dictionary overview',
pathname: '',