From a8b44b09a404aee477e735501b828d1b14aad311 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Tue, 7 Jun 2022 01:33:52 +0200 Subject: feat: Add inital translation support --- apps/projects/src/.typesafe-i18n.json | 5 + apps/projects/src/app/index.scss | 1 + apps/projects/src/app/index.svelte | 123 ++-- apps/projects/src/app/lib/i18n/en/index.ts | 127 ++++ apps/projects/src/app/lib/i18n/formatters.ts | 11 + apps/projects/src/app/lib/i18n/i18n-svelte.ts | 12 + apps/projects/src/app/lib/i18n/i18n-types.ts | 812 +++++++++++++++++++++ apps/projects/src/app/lib/i18n/i18n-util.async.ts | 27 + apps/projects/src/app/lib/i18n/i18n-util.sync.ts | 27 + apps/projects/src/app/lib/i18n/i18n-util.ts | 31 + apps/projects/src/app/lib/i18n/nb/index.ts | 127 ++++ apps/projects/src/app/lib/stores/locale.ts | 21 + apps/projects/src/app/pages/_layout.svelte | 32 +- apps/projects/src/app/pages/data.svelte | 34 +- apps/projects/src/app/pages/home.svelte | 27 +- apps/projects/src/app/pages/not-found.svelte | 5 +- .../src/app/pages/views/category-form/index.svelte | 12 +- .../app/pages/views/data-table-paginator.svelte | 30 +- .../src/app/pages/views/entry-form/index.svelte | 13 +- .../views/entry-form/sections/category.svelte | 11 +- .../views/entry-form/sections/date-time.svelte | 233 +++--- .../pages/views/entry-form/sections/labels.svelte | 9 +- .../pages/views/settings-categories-tile.svelte | 21 +- .../app/pages/views/settings-labels-tile.svelte | 19 +- apps/projects/src/package.json | 8 +- apps/projects/src/pnpm-lock.yaml | 500 +++++++++++++ 26 files changed, 2016 insertions(+), 262 deletions(-) create mode 100644 apps/projects/src/.typesafe-i18n.json create mode 100644 apps/projects/src/app/lib/i18n/en/index.ts create mode 100644 apps/projects/src/app/lib/i18n/formatters.ts create mode 100644 apps/projects/src/app/lib/i18n/i18n-svelte.ts create mode 100644 apps/projects/src/app/lib/i18n/i18n-types.ts create mode 100644 apps/projects/src/app/lib/i18n/i18n-util.async.ts create mode 100644 apps/projects/src/app/lib/i18n/i18n-util.sync.ts create mode 100644 apps/projects/src/app/lib/i18n/i18n-util.ts create mode 100644 apps/projects/src/app/lib/i18n/nb/index.ts create mode 100644 apps/projects/src/app/lib/stores/locale.ts (limited to 'apps/projects/src') diff --git a/apps/projects/src/.typesafe-i18n.json b/apps/projects/src/.typesafe-i18n.json new file mode 100644 index 0000000..cfb94b6 --- /dev/null +++ b/apps/projects/src/.typesafe-i18n.json @@ -0,0 +1,5 @@ +{ + "adapter": "svelte", + "$schema": "https://unpkg.com/typesafe-i18n@5.5.2/schema/typesafe-i18n.json", + "outputPath": "app/lib/i18n" +} diff --git a/apps/projects/src/app/index.scss b/apps/projects/src/app/index.scss index 4794787..0892d63 100644 --- a/apps/projects/src/app/index.scss +++ b/apps/projects/src/app/index.scss @@ -36,3 +36,4 @@ @use '../../web-shared/src/styles/components/custom-checkbox'; @use '../../web-shared/src/styles/components/menu'; @use '../../web-shared/src/styles/components/user-menu'; +@use '../../web-shared/src/styles/components/light-dark-switch'; diff --git a/apps/projects/src/app/index.svelte b/apps/projects/src/app/index.svelte index 77d290d..e397de3 100644 --- a/apps/projects/src/app/index.svelte +++ b/apps/projects/src/app/index.svelte @@ -1,53 +1,86 @@ - -You seem to be offline, please check your internet connection. +{notOnlineText} = (locale: Locales) => { + + const formatters: Formatters = { + // add your formatter functions here + } + + return formatters +} diff --git a/apps/projects/src/app/lib/i18n/i18n-svelte.ts b/apps/projects/src/app/lib/i18n/i18n-svelte.ts new file mode 100644 index 0000000..6cdffb3 --- /dev/null +++ b/apps/projects/src/app/lib/i18n/i18n-svelte.ts @@ -0,0 +1,12 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initI18nSvelte } from 'typesafe-i18n/svelte' +import type { Formatters, Locales, TranslationFunctions, Translations } from './i18n-types' +import { loadedFormatters, loadedLocales } from './i18n-util' + +const { locale, LL, setLocale } = initI18nSvelte(loadedLocales, loadedFormatters) + +export { locale, LL, setLocale } + +export default LL diff --git a/apps/projects/src/app/lib/i18n/i18n-types.ts b/apps/projects/src/app/lib/i18n/i18n-types.ts new file mode 100644 index 0000000..f9fd9cc --- /dev/null +++ b/apps/projects/src/app/lib/i18n/i18n-types.ts @@ -0,0 +1,812 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ +import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n' + +export type BaseTranslation = BaseTranslationType +export type BaseLocale = 'en' + +export type Locales = + | 'en' + | 'nb' + +export type Translation = RootTranslation + +export type Translations = RootTranslation + +type RootTranslation = { + nav: { + /** + * Home + */ + home: string + /** + * Data + */ + data: string + /** + * Settings + */ + settings: string + usermenu: { + /** + * Log out + */ + logout: string + /** + * Log out of your profile + */ + logoutTitle: string + /** + * Profile + */ + profile: string + /** + * Administrate your profile + */ + profileTitle: string + /** + * Toggle user menu + */ + toggleTitle: string + } + } + views: { + dataTablePaginator: { + /** + * Go to previous page + */ + goToPrevPage: string + /** + * Go to next page + */ + goToNextPage: string + /** + * of + */ + of: string + } + categoryForm: { + /** + * Name + */ + name: string + /** + * Color + */ + color: string + /** + * Default labels + */ + defaultLabels: string + /** + * Search or create + */ + labelsPlaceholder: string + } + settingsCategoriesTile: { + /** + * Are you sure you want to delete this category? + This will delete all relating entries! + */ + deleteAllConfirm: string + /** + * Active + */ + active: string + /** + * Archived + */ + archived: string + /** + * Name + */ + name: string + /** + * Color + */ + color: string + /** + * Edit entry + */ + editEntry: string + /** + * Delete entry + */ + deleteEntry: string + /** + * No categories + */ + noCategories: string + } + settingsLabelsTile: { + /** + * Are you sure you want to delete this label? + It will be removed from all related entries! + */ + deleteAllConfirm: string + /** + * Active + */ + active: string + /** + * Archived + */ + archived: string + /** + * Name + */ + name: string + /** + * Color + */ + color: string + /** + * Edit label + */ + editEntry: string + /** + * Delete label + */ + deleteEntry: string + /** + * No labels + */ + noLabels: string + } + entryForm: { + /** + * An error occured while updating the entry, try again soon. + */ + entryUpdateError: string + /** + * An error occured while creating the entry, try again soon. + */ + entryCreateError: string + /** + * Description is required + */ + errDescriptionReq: string + /** + * Reset + */ + reset: string + /** + * Description + */ + description: string + /** + * Save + */ + save: string + /** + * Create + */ + create: string + category: { + /** + * Category + */ + category: string + /** + * Search or create + */ + placeholder: string + /** + * No categories available (Create a new one by searching for it) + */ + noResults: string + /** + * Category is required + */ + errisRequired: string + /** + * Reset category section + */ + _logReset: string + } + labels: { + /** + * Search or create + */ + placeholder: string + /** + * No labels available (Create a new one by searching for it) + */ + noResults: string + /** + * Labels + */ + labels: string + /** + * Reset labels section + */ + _logReset: string + } + dateTime: { + /** + * Date is required + */ + errDateIsRequired: string + /** + * From is required + */ + errFromIsRequired: string + /** + * From can not be after To + */ + errFromAfterTo: string + /** + * From and To can not be equal + */ + errFromEqTo: string + /** + * To is required + */ + errToIsRequired: string + /** + * To can not be before From + */ + errToBeforeFrom: string + /** + * From + */ + from: string + /** + * To + */ + to: string + /** + * Date + */ + date: string + /** + * Reset date time section + */ + _logReset: string + } + } + } + data: { + /** + * Showing {entryCountString}, totalling in {totalHourMin} + * @param {string} entryCountString + * @param {string} totalHourMin + */ + durationSummary: RequiredParams<'entryCountString' | 'totalHourMin'> + /** + * h + */ + hourSingleChar: string + /** + * m + */ + minSingleChar: string + /** + * entry + */ + entry: string + /** + * entries + */ + entries: string + /** + * Are you sure you want to delete this entry? + */ + confirmDeleteEntry: string + /** + * Edit entry + */ + editEntry: string + /** + * Date + */ + date: string + /** + * From + */ + from: string + /** + * Duration + */ + duration: string + /** + * Category + */ + category: string + /** + * Description + */ + description: string + /** + * Loading + */ + loading: string + /** + * No entries + */ + noEntries: string + /** + * to + */ + to: string + /** + * Use + */ + use: string + } + home: { + /** + * h + */ + hourSingleChar: string + /** + * m + */ + minSingleChar: string + /** + * Are you sure you want to delete this entry? + */ + confirmDeleteEntry: string + /** + * New entry + */ + newEntry: string + /** + * Edit entry + */ + editEntry: string + /** + * Delete entry + */ + deleteEntry: string + /** + * Logged time today + */ + loggedTimeToday: string + /** + * Current time + */ + currentTime: string + /** + * Loading + */ + loading: string + /** + * Stopwatch + */ + stopwatch: string + /** + * Today's entries + */ + todayEntries: string + /** + * No entries today + */ + noEntriesToday: string + /** + * Refresh today's entries + */ + refreshTodayEntries: string + /** + * Category + */ + category: string + /** + * Timespan + */ + timespan: string + } + messages: { + /** + * Page not found + */ + pageNotFound: string + /** + * Go to frontpage + */ + goToFrontpage: string + /** + * It seems like your device does not have a internet connection, please check your connection. + */ + noInternet: string + } +} + +export type TranslationFunctions = { + nav: { + /** + * Home + */ + home: () => LocalizedString + /** + * Data + */ + data: () => LocalizedString + /** + * Settings + */ + settings: () => LocalizedString + usermenu: { + /** + * Log out + */ + logout: () => LocalizedString + /** + * Log out of your profile + */ + logoutTitle: () => LocalizedString + /** + * Profile + */ + profile: () => LocalizedString + /** + * Administrate your profile + */ + profileTitle: () => LocalizedString + /** + * Toggle user menu + */ + toggleTitle: () => LocalizedString + } + } + views: { + dataTablePaginator: { + /** + * Go to previous page + */ + goToPrevPage: () => LocalizedString + /** + * Go to next page + */ + goToNextPage: () => LocalizedString + /** + * of + */ + of: () => LocalizedString + } + categoryForm: { + /** + * Name + */ + name: () => LocalizedString + /** + * Color + */ + color: () => LocalizedString + /** + * Default labels + */ + defaultLabels: () => LocalizedString + /** + * Search or create + */ + labelsPlaceholder: () => LocalizedString + } + settingsCategoriesTile: { + /** + * Are you sure you want to delete this category? + This will delete all relating entries! + */ + deleteAllConfirm: () => LocalizedString + /** + * Active + */ + active: () => LocalizedString + /** + * Archived + */ + archived: () => LocalizedString + /** + * Name + */ + name: () => LocalizedString + /** + * Color + */ + color: () => LocalizedString + /** + * Edit entry + */ + editEntry: () => LocalizedString + /** + * Delete entry + */ + deleteEntry: () => LocalizedString + /** + * No categories + */ + noCategories: () => LocalizedString + } + settingsLabelsTile: { + /** + * Are you sure you want to delete this label? + It will be removed from all related entries! + */ + deleteAllConfirm: () => LocalizedString + /** + * Active + */ + active: () => LocalizedString + /** + * Archived + */ + archived: () => LocalizedString + /** + * Name + */ + name: () => LocalizedString + /** + * Color + */ + color: () => LocalizedString + /** + * Edit label + */ + editEntry: () => LocalizedString + /** + * Delete label + */ + deleteEntry: () => LocalizedString + /** + * No labels + */ + noLabels: () => LocalizedString + } + entryForm: { + /** + * An error occured while updating the entry, try again soon. + */ + entryUpdateError: () => LocalizedString + /** + * An error occured while creating the entry, try again soon. + */ + entryCreateError: () => LocalizedString + /** + * Description is required + */ + errDescriptionReq: () => LocalizedString + /** + * Reset + */ + reset: () => LocalizedString + /** + * Description + */ + description: () => LocalizedString + /** + * Save + */ + save: () => LocalizedString + /** + * Create + */ + create: () => LocalizedString + category: { + /** + * Category + */ + category: () => LocalizedString + /** + * Search or create + */ + placeholder: () => LocalizedString + /** + * No categories available (Create a new one by searching for it) + */ + noResults: () => LocalizedString + /** + * Category is required + */ + errisRequired: () => LocalizedString + /** + * Reset category section + */ + _logReset: () => LocalizedString + } + labels: { + /** + * Search or create + */ + placeholder: () => LocalizedString + /** + * No labels available (Create a new one by searching for it) + */ + noResults: () => LocalizedString + /** + * Labels + */ + labels: () => LocalizedString + /** + * Reset labels section + */ + _logReset: () => LocalizedString + } + dateTime: { + /** + * Date is required + */ + errDateIsRequired: () => LocalizedString + /** + * From is required + */ + errFromIsRequired: () => LocalizedString + /** + * From can not be after To + */ + errFromAfterTo: () => LocalizedString + /** + * From and To can not be equal + */ + errFromEqTo: () => LocalizedString + /** + * To is required + */ + errToIsRequired: () => LocalizedString + /** + * To can not be before From + */ + errToBeforeFrom: () => LocalizedString + /** + * From + */ + from: () => LocalizedString + /** + * To + */ + to: () => LocalizedString + /** + * Date + */ + date: () => LocalizedString + /** + * Reset date time section + */ + _logReset: () => LocalizedString + } + } + } + data: { + /** + * Showing {entryCountString}, totalling in {totalHourMin} + */ + durationSummary: (arg: { entryCountString: string, totalHourMin: string }) => LocalizedString + /** + * h + */ + hourSingleChar: () => LocalizedString + /** + * m + */ + minSingleChar: () => LocalizedString + /** + * entry + */ + entry: () => LocalizedString + /** + * entries + */ + entries: () => LocalizedString + /** + * Are you sure you want to delete this entry? + */ + confirmDeleteEntry: () => LocalizedString + /** + * Edit entry + */ + editEntry: () => LocalizedString + /** + * Date + */ + date: () => LocalizedString + /** + * From + */ + from: () => LocalizedString + /** + * Duration + */ + duration: () => LocalizedString + /** + * Category + */ + category: () => LocalizedString + /** + * Description + */ + description: () => LocalizedString + /** + * Loading + */ + loading: () => LocalizedString + /** + * No entries + */ + noEntries: () => LocalizedString + /** + * to + */ + to: () => LocalizedString + /** + * Use + */ + use: () => LocalizedString + } + home: { + /** + * h + */ + hourSingleChar: () => LocalizedString + /** + * m + */ + minSingleChar: () => LocalizedString + /** + * Are you sure you want to delete this entry? + */ + confirmDeleteEntry: () => LocalizedString + /** + * New entry + */ + newEntry: () => LocalizedString + /** + * Edit entry + */ + editEntry: () => LocalizedString + /** + * Delete entry + */ + deleteEntry: () => LocalizedString + /** + * Logged time today + */ + loggedTimeToday: () => LocalizedString + /** + * Current time + */ + currentTime: () => LocalizedString + /** + * Loading + */ + loading: () => LocalizedString + /** + * Stopwatch + */ + stopwatch: () => LocalizedString + /** + * Today's entries + */ + todayEntries: () => LocalizedString + /** + * No entries today + */ + noEntriesToday: () => LocalizedString + /** + * Refresh today's entries + */ + refreshTodayEntries: () => LocalizedString + /** + * Category + */ + category: () => LocalizedString + /** + * Timespan + */ + timespan: () => LocalizedString + } + messages: { + /** + * Page not found + */ + pageNotFound: () => LocalizedString + /** + * Go to frontpage + */ + goToFrontpage: () => LocalizedString + /** + * It seems like your device does not have a internet connection, please check your connection. + */ + noInternet: () => LocalizedString + } +} + +export type Formatters = {} diff --git a/apps/projects/src/app/lib/i18n/i18n-util.async.ts b/apps/projects/src/app/lib/i18n/i18n-util.async.ts new file mode 100644 index 0000000..75b90c9 --- /dev/null +++ b/apps/projects/src/app/lib/i18n/i18n-util.async.ts @@ -0,0 +1,27 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters' +import type { Locales, Translations } from './i18n-types' +import { loadedFormatters, loadedLocales, locales } from './i18n-util' + +const localeTranslationLoaders = { + en: () => import('./en'), + nb: () => import('./nb'), +} + +const updateDictionary = (locale: Locales, dictionary: Partial) => + loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } + +export const loadLocaleAsync = async (locale: Locales): Promise => { + updateDictionary( + locale, + (await localeTranslationLoaders[locale]()).default as unknown as Translations + ) + loadFormatters(locale) +} + +export const loadAllLocalesAsync = (): Promise => Promise.all(locales.map(loadLocaleAsync)) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/apps/projects/src/app/lib/i18n/i18n-util.sync.ts b/apps/projects/src/app/lib/i18n/i18n-util.sync.ts new file mode 100644 index 0000000..7a1d51e --- /dev/null +++ b/apps/projects/src/app/lib/i18n/i18n-util.sync.ts @@ -0,0 +1,27 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters' +import type { Locales, Translations } from './i18n-types' +import { loadedFormatters, loadedLocales, locales } from './i18n-util' + +import en from './en' +import nb from './nb' + +const localeTranslations = { + en, + nb, +} + +export const loadLocale = (locale: Locales): void => { + if (loadedLocales[locale]) return + + loadedLocales[locale] = localeTranslations[locale] as unknown as Translations + loadFormatters(locale) +} + +export const loadAllLocales = (): void => locales.forEach(loadLocale) + +export const loadFormatters = (locale: Locales): void => { + loadedFormatters[locale] = initFormatters(locale) +} diff --git a/apps/projects/src/app/lib/i18n/i18n-util.ts b/apps/projects/src/app/lib/i18n/i18n-util.ts new file mode 100644 index 0000000..cad1e7a --- /dev/null +++ b/apps/projects/src/app/lib/i18n/i18n-util.ts @@ -0,0 +1,31 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' +import type { LocaleDetector } from 'typesafe-i18n/detectors' +import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' +import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types' + +export const baseLocale: Locales = 'en' + +export const locales: Locales[] = [ + 'en', + 'nb' +] + +export const loadedLocales = {} as Record + +export const loadedFormatters = {} as Record + +export const i18nString = (locale: Locales) => initI18nString(locale, loadedFormatters[locale]) + +export const i18nObject = (locale: Locales) => + initI18nObject( + locale, + loadedLocales[locale], + loadedFormatters[locale] + ) + +export const i18n = () => initI18n(loadedLocales, loadedFormatters) + +export const detectLocale = (...detectors: LocaleDetector[]) => detectLocaleFn(baseLocale, locales, ...detectors) diff --git a/apps/projects/src/app/lib/i18n/nb/index.ts b/apps/projects/src/app/lib/i18n/nb/index.ts new file mode 100644 index 0000000..af3a487 --- /dev/null +++ b/apps/projects/src/app/lib/i18n/nb/index.ts @@ -0,0 +1,127 @@ +import type {Translation} from "../i18n-types"; + +const nb: Translation = { + nav: { + home: "Hjem", + data: "Data", + settings: "Innstillinger", + usermenu: { + logout: "Logg ut", + logoutTitle: "Logg ut av din profil", + profile: "Profil", + profileTitle: "Administrer din profil", + toggleTitle: "Vis brukermeny" + } + }, + views: { + categoryForm: { + name: "Navn", + color: "Farge", + defaultLabels: "Standard merknader", + labelsPlaceholder: "Søk eller opprett" + }, + dataTablePaginator: { + goToPrevPage: "Gå til forrige side", + goToNextPage: "Gå til neste side", + of: "av", + }, + settingsCategoriesTile: { + deleteAllConfirm: "Er du sikker på at du vil slette denne kategorien?\nDette vil slette alle tilhørende rader!", + active: "Aktive", + archived: "Arkiverte", + name: "Navn", + color: "Farge", + editEntry: "Rediger kategori", + deleteEntry: "Slett kategori", + noCategories: "Ingen kategorier", + categories: "Kategorier" + }, + settingsLabelsTile: { + deleteAllConfirm: "Er du sikker på at du vil slette denne merknaden?\nDen vil bli slette fra alle relaterte rader!", + active: "Aktive", + archived: "Arkiverte", + name: "Navn", + color: "Farge", + editEntry: "Rediger merknad", + deleteEntry: "Slett merknad", + noLabels: "Ingen merknader", + labels: "Merknader" + }, + entryForm: { + entryUpdateError: "En feil oppstod med lagringen av din rad, prøv igjen snart.", + entryCreateError: "En feil oppstod med opprettelsen av din rad, prøv igjen snart.", + errDescriptionReq: "Beskrivelse er påkrevd", + reset: "Tilbakestill", + description: "Beskrivelse", + save: "Lagre", + create: "Opprett", + category: { + category: "Kategori", + placeholder: "Søk eller opprett", + noResults: "Ingen kategorier tilgjengelig (Opprett en ny ved å skrive navnet i søkefeltet).", + errisRequired: "Kategori er påkrevd", + _logReset: "Tilbakestilte kategori-seksjonen" + }, + labels: { + placeholder: "Søk eller opprett", + noResults: "Ingen merkander tilgjengelig (Opprett en ny ved å skrive navnet i søkefeltet).", + labels: "Merknader", + _logReset: "Tilbakestilte merknader-seksjonen" + }, + dateTime: { + errDateIsRequired: "Dato er påkrevd", + errFromIsRequired: "Fra er påkrevd", + errFromAfterTo: "Fra kan ikke være etter Til", + errFromEqTo: "Fra og Til kan ikke ha lik verdi", + errToIsRequired: "Til er påkrevd", + errToBeforeFrom: "Til kan ikke være før Fra", + from: "Fra", + to: "Til", + date: "Dato", + _logReset: "Tilbakestilte dato-seksjonen" + } + } + }, + data: { + durationSummary: "Viser {entryCountString:string}, Tilsammen {totalHourMin:string}", + hourSingleChar: "t", + minSingleChar: "m", + entry: "rad", + entries: "rader", + confirmDeleteEntry: "Er du sikker på at du vil slette denne raden?", + editEntry: "Rediger rad", + date: "Dato", + from: "Fra", + duration: "Tidsrom", + category: "Kategori", + description: "Beskrivelse", + loading: "Laster", + noEntries: "Ingen rader", + to: "til", + use: "Bruk", + }, + home: { + hourSingleChar: "t", + minSingleChar: "m", + confirmDeleteEntry: "Er du sikker på at du vil slette denne raden?", + newEntry: "Ny rad", + editEntry: "Rediger rad", + deleteEntry: "Slett rad", + loggedTimeToday: "Registrert tid hittil idag", + currentTime: "Klokken", + loading: "Laster", + stopwatch: "Stoppeklokke", + todayEntries: "Dagens rader", + noEntriesToday: "Ingen rader i dag", + refreshTodayEntries: "Last inn dagens rader på nytt", + category: "Kategori", + timespan: "Tidsrom", + }, + messages: { + pageNotFound: "Fant ikke siden", + goToFrontpage: "Gå til forsiden", + noInternet: "Det ser ut som at du er uten internettilgang, vennligst sjekk tilkoblingen din." + } +}; + +export default nb; diff --git a/apps/projects/src/app/lib/stores/locale.ts b/apps/projects/src/app/lib/stores/locale.ts new file mode 100644 index 0000000..1215c20 --- /dev/null +++ b/apps/projects/src/app/lib/stores/locale.ts @@ -0,0 +1,21 @@ +import {base_domain, CookieNames} from "$shared/lib/configuration"; +import {get_cookie, set_cookie} from "$shared/lib/helpers"; +import {writable} from "svelte/store"; +import type {Locales} from "$app/lib/i18n/i18n-types"; + +export function preffered_or_default(): Locales { + if (/^en\b/i.test(navigator.language)) { + return "en"; + } + if (/^nb\b/i.test(navigator.language) || /^nn\b/i.test(navigator.language)) { + return "nb"; + } + return "en"; +} + +export const currentLocale = writable((get_cookie(CookieNames.locale) ?? preffered_or_default()) as Locales); +currentLocale.subscribe(locale => { + //@ts-ignore + if (locale === "preffered") set_cookie(CookieNames.locale, preffered_or_default(), base_domain()); + set_cookie(CookieNames.locale, locale, base_domain()); +}); diff --git a/apps/projects/src/app/pages/_layout.svelte b/apps/projects/src/app/pages/_layout.svelte index 3d632ae..fb34593 100644 --- a/apps/projects/src/app/pages/_layout.svelte +++ b/apps/projects/src/app/pages/_layout.svelte @@ -2,18 +2,27 @@ import {onMount} from "svelte"; import {location, link} from "svelte-spa-router"; import {logout_user} from "$app/lib/services/user-service"; - import {random_string, switch_theme} from "$shared/lib/helpers"; + import {random_string} from "$shared/lib/helpers"; import {get_session_data} from "$shared/lib/session"; import ProfileModal from "$app/pages/views/profile-modal.svelte"; import {Menu, MenuItem, MenuItemSeparator} from "$shared/components/menu"; import Button from "$shared/components/button.svelte"; import {IconNames} from "$shared/lib/configuration"; + import LL from "$app/lib/i18n/i18n-svelte"; + import BlowoutToolbelt from "$shared/components/blowout-toolbelt.svelte"; + import {currentLocale} from "$app/lib/stores/locale"; let ProfileModalFunctions = {}; let showUserMenu = false; let userMenuTriggerNode; const userMenuId = "__menu_" + random_string(3); - const username = get_session_data().profile.username; + const username = get_session_data()?.profile.username; + + function toolbelt_change(event) { + if (event.detail.name === "locale") { + currentLocale.set(event.detail.value); + } + } onMount(() => { userMenuTriggerNode = document.getElementById("open-user-menu"); @@ -21,6 +30,7 @@ +