From 21fd77ed37bfe5a88e5d0e1b4e9a231554d4c59e Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Thu, 18 Mar 2021 13:08:59 +0100 Subject: [PATCH 1/4] feat: toggle lang and load selected lang at startup --- studio/src/app/app-root.tsx | 4 ++ studio/src/app/definitions/i18.d.ts | 2 +- .../app-customization/app-customization.tsx | 57 +++++++++++++------ studio/src/app/services/lang/lang.service.ts | 30 ++++++++++ studio/src/app/stores/i18n.store.ts | 39 ++++++++++++- 5 files changed, 111 insertions(+), 21 deletions(-) create mode 100644 studio/src/app/services/lang/lang.service.ts diff --git a/studio/src/app/app-root.tsx b/studio/src/app/app-root.tsx index 9a50dd66c..a658a3855 100644 --- a/studio/src/app/app-root.tsx +++ b/studio/src/app/app-root.tsx @@ -13,6 +13,7 @@ import {OfflineService} from './services/editor/offline/offline.service'; import {NavDirection, NavParams} from './stores/nav.store'; import {ColorService} from './services/color/color.service'; import {SettingsService} from './services/settings/settings.service'; +import {LangService} from './services/lang/lang.service'; @Component({ tag: 'app-root', @@ -27,6 +28,7 @@ export class AppRoot { private readonly themeService: ThemeService; private readonly colorService: ColorService; private readonly settingsService: SettingsService; + private readonly langService: LangService; @State() private loading: boolean = true; @@ -43,6 +45,7 @@ export class AppRoot { this.themeService = ThemeService.getInstance(); this.colorService = ColorService.getInstance(); this.settingsService = SettingsService.getInstance(); + this.langService = LangService.getInstance(); } async componentWillLoad() { @@ -53,6 +56,7 @@ export class AppRoot { this.themeService.initDarkModePreference(), this.colorService.init(), this.settingsService.init(), + this.langService.init(), ]; await Promise.all(promises); diff --git a/studio/src/app/definitions/i18.d.ts b/studio/src/app/definitions/i18.d.ts index 9b4de7d41..6b7bf2a04 100644 --- a/studio/src/app/definitions/i18.d.ts +++ b/studio/src/app/definitions/i18.d.ts @@ -487,7 +487,7 @@ interface I18nPoll { } interface I18n { - lang: 'en'; + lang: 'en' | 'es'; core: I18nCore; nav: I18nNav; menu: I18nMenu; diff --git a/studio/src/app/pages/core/settings/app-customization/app-customization.tsx b/studio/src/app/pages/core/settings/app-customization/app-customization.tsx index 847c4838b..1ad08a1ce 100644 --- a/studio/src/app/pages/core/settings/app-customization/app-customization.tsx +++ b/studio/src/app/pages/core/settings/app-customization/app-customization.tsx @@ -1,4 +1,4 @@ -import {Component, Fragment, h} from '@stencil/core'; +import {Component, h} from '@stencil/core'; import themeStore from '../../../../stores/theme.store'; import settingsStore from '../../../../stores/settings.store'; @@ -29,6 +29,10 @@ export class AppCustomization { settingsStore.state.editMode = settingsStore.state.editMode === 'css' ? 'properties' : 'css'; } + private toggleLang($event: CustomEvent) { + i18n.state.lang = $event.detail.value; + } + render() { return [ , @@ -39,6 +43,8 @@ export class AppCustomization { {this.renderDarkLightToggle()} + {this.renderLang()} + {this.renderEditMode()} @@ -57,25 +63,40 @@ export class AppCustomization { ); } + private renderLang() { + return ( + + {i18n.state.editor.language} + this.toggleLang($event)} + interface="popover" + mode="md" + class="ion-padding-start ion-padding-end"> + English + EspaƱol + + + ); + } + private renderEditMode() { return ( - - - {i18n.state.settings.edit_mode} - - - this.toggleEditMode()}> - - - {i18n.state.settings.properties} - - - - - CSS - - - + + {i18n.state.settings.edit_mode} + + this.toggleEditMode()} + interface="popover" + mode="md" + class="ion-padding-start ion-padding-end"> + {i18n.state.settings.properties} + CSS + + ); } } diff --git a/studio/src/app/services/lang/lang.service.ts b/studio/src/app/services/lang/lang.service.ts new file mode 100644 index 000000000..b4c2a75a1 --- /dev/null +++ b/studio/src/app/services/lang/lang.service.ts @@ -0,0 +1,30 @@ +import i18n from '../../stores/i18n.store'; + +import {get} from 'idb-keyval'; + +export class LangService { + private static instance: LangService; + + private constructor() { + // Private constructor, singleton + } + + static getInstance() { + if (!LangService.instance) { + LangService.instance = new LangService(); + } + return LangService.instance; + } + + async init() { + try { + const lang: 'en' | 'es' | null = await get<'en' | 'es'>('deckdeckgo_lang'); + + if (lang) { + i18n.state.lang = lang; + } + } catch (err) { + console.warn(`Couldn't find lang. Proceeding with default`); + } + } +} diff --git a/studio/src/app/stores/i18n.store.ts b/studio/src/app/stores/i18n.store.ts index 6d76448cc..a3e545180 100644 --- a/studio/src/app/stores/i18n.store.ts +++ b/studio/src/app/stores/i18n.store.ts @@ -1,10 +1,45 @@ import {createStore} from '@stencil/store'; import en from '../../assets/i18n/en.json'; +import {set} from 'idb-keyval'; -const {state} = createStore({ - lang: en, +const {state, onChange} = createStore({ + lang: 'en', ...(en as Partial), } as I18n); +const esI18n = async (): Promise => { + return { + lang: 'es', + ...(await import(`../../assets/i18n/es.json`)), + }; +}; + +const enI18n = (): I18n => { + return { + lang: 'en', + ...(en as Partial), + } as I18n; +}; + +onChange('lang', async (lang: 'en' | 'es') => { + let bundle: I18n; + + switch (lang) { + case 'es': + bundle = await esI18n(); + break; + default: + bundle = enI18n(); + } + + Object.assign(state, bundle); +}); + +onChange('lang', (lang: 'en' | 'es') => { + set('deckdeckgo_lang', lang).catch((err) => { + console.error('Failed to update IDB with new language', err); + }); +}); + export default {state}; From 56cc1f504529b537bebf13f4ad8738d60078d320 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Thu, 18 Mar 2021 13:31:56 +0100 Subject: [PATCH 2/4] feat: init default browser language --- studio/src/app/services/lang/lang.service.ts | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/studio/src/app/services/lang/lang.service.ts b/studio/src/app/services/lang/lang.service.ts index b4c2a75a1..f6ecfa87a 100644 --- a/studio/src/app/services/lang/lang.service.ts +++ b/studio/src/app/services/lang/lang.service.ts @@ -22,9 +22,46 @@ export class LangService { if (lang) { i18n.state.lang = lang; + return; } + + this.initDefaultLang(); } catch (err) { console.warn(`Couldn't find lang. Proceeding with default`); } } + + private initDefaultLang() { + const browserLang: string | undefined = this.getBrowserLang(); + i18n.state.lang = /(es|en)/gi.test(browserLang) ? (browserLang as 'en' | 'es') : 'en'; + } + + /** + * From ngx-translate + * https://github.com/ngx-translate/core/blob/efcb4f43a645d9ac630aae8e50b60cc883e675fd/projects/ngx-translate/core/src/lib/translate.service.ts + * @private + */ + private getBrowserLang(): string | undefined { + if (typeof window === 'undefined' || typeof window.navigator === 'undefined') { + return undefined; + } + + let browserLang: string | null = window.navigator.languages ? window.navigator.languages[0] : null; + // @ts-ignore + browserLang = browserLang || window.navigator.language || window.navigator.browserLanguage || window.navigator.userLanguage; + + if (typeof browserLang === 'undefined') { + return undefined; + } + + if (browserLang.indexOf('-') !== -1) { + browserLang = browserLang.split('-')[0]; + } + + if (browserLang.indexOf('_') !== -1) { + browserLang = browserLang.split('_')[0]; + } + + return browserLang; + } } From 1043366674643eb7d09708fc4cd5e25c8d9634c8 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Thu, 18 Mar 2021 13:36:28 +0100 Subject: [PATCH 3/4] build: add spanish to supported languages --- studio/scripts/i18n.types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/scripts/i18n.types.js b/studio/scripts/i18n.types.js index ef80660c0..45983c1bb 100644 --- a/studio/scripts/i18n.types.js +++ b/studio/scripts/i18n.types.js @@ -16,7 +16,7 @@ const generate = async () => { }; }); - const lang = `lang: 'en';`; + const lang = `lang: 'en' | 'es';`; const main = `\n\ninterface I18n {${lang}${data.map((i) => `${i.key}: ${i.name};`).join('')}}`; const interfaces = data.map((i) => `\n\ninterface ${i.name} {${i.properties.join('')}}`).join(''); From 975cad4ab0b333efb9efe4fe1d0b51f89a46f912 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Thu, 18 Mar 2021 13:47:21 +0100 Subject: [PATCH 4/4] build: supported languages type declaration --- studio/scripts/i18n.types.js | 2 +- studio/src/app/definitions/i18.d.ts | 2 +- studio/src/app/definitions/languages.d.ts | 1 + studio/src/app/services/lang/lang.service.ts | 4 ++-- studio/src/app/stores/i18n.store.ts | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 studio/src/app/definitions/languages.d.ts diff --git a/studio/scripts/i18n.types.js b/studio/scripts/i18n.types.js index 45983c1bb..094634a63 100644 --- a/studio/scripts/i18n.types.js +++ b/studio/scripts/i18n.types.js @@ -16,7 +16,7 @@ const generate = async () => { }; }); - const lang = `lang: 'en' | 'es';`; + const lang = `lang: Languages;`; const main = `\n\ninterface I18n {${lang}${data.map((i) => `${i.key}: ${i.name};`).join('')}}`; const interfaces = data.map((i) => `\n\ninterface ${i.name} {${i.properties.join('')}}`).join(''); diff --git a/studio/src/app/definitions/i18.d.ts b/studio/src/app/definitions/i18.d.ts index 6b7bf2a04..1759408e3 100644 --- a/studio/src/app/definitions/i18.d.ts +++ b/studio/src/app/definitions/i18.d.ts @@ -487,7 +487,7 @@ interface I18nPoll { } interface I18n { - lang: 'en' | 'es'; + lang: Languages; core: I18nCore; nav: I18nNav; menu: I18nMenu; diff --git a/studio/src/app/definitions/languages.d.ts b/studio/src/app/definitions/languages.d.ts new file mode 100644 index 000000000..b9acd5480 --- /dev/null +++ b/studio/src/app/definitions/languages.d.ts @@ -0,0 +1 @@ +type Languages = 'en' | 'es'; diff --git a/studio/src/app/services/lang/lang.service.ts b/studio/src/app/services/lang/lang.service.ts index f6ecfa87a..0c697520c 100644 --- a/studio/src/app/services/lang/lang.service.ts +++ b/studio/src/app/services/lang/lang.service.ts @@ -18,7 +18,7 @@ export class LangService { async init() { try { - const lang: 'en' | 'es' | null = await get<'en' | 'es'>('deckdeckgo_lang'); + const lang: Languages | null = await get('deckdeckgo_lang'); if (lang) { i18n.state.lang = lang; @@ -33,7 +33,7 @@ export class LangService { private initDefaultLang() { const browserLang: string | undefined = this.getBrowserLang(); - i18n.state.lang = /(es|en)/gi.test(browserLang) ? (browserLang as 'en' | 'es') : 'en'; + i18n.state.lang = /(es|en)/gi.test(browserLang) ? (browserLang as Languages) : 'en'; } /** diff --git a/studio/src/app/stores/i18n.store.ts b/studio/src/app/stores/i18n.store.ts index a3e545180..8d61d4917 100644 --- a/studio/src/app/stores/i18n.store.ts +++ b/studio/src/app/stores/i18n.store.ts @@ -22,7 +22,7 @@ const enI18n = (): I18n => { } as I18n; }; -onChange('lang', async (lang: 'en' | 'es') => { +onChange('lang', async (lang: Languages) => { let bundle: I18n; switch (lang) { @@ -36,7 +36,7 @@ onChange('lang', async (lang: 'en' | 'es') => { Object.assign(state, bundle); }); -onChange('lang', (lang: 'en' | 'es') => { +onChange('lang', (lang: Languages) => { set('deckdeckgo_lang', lang).catch((err) => { console.error('Failed to update IDB with new language', err); });