summaryrefslogtreecommitdiffstats
path: root/apps/projects/src/app/lib
diff options
context:
space:
mode:
Diffstat (limited to 'apps/projects/src/app/lib')
-rw-r--r--apps/projects/src/app/lib/i18n/en/index.ts127
-rw-r--r--apps/projects/src/app/lib/i18n/formatters.ts11
-rw-r--r--apps/projects/src/app/lib/i18n/i18n-svelte.ts12
-rw-r--r--apps/projects/src/app/lib/i18n/i18n-types.ts812
-rw-r--r--apps/projects/src/app/lib/i18n/i18n-util.async.ts27
-rw-r--r--apps/projects/src/app/lib/i18n/i18n-util.sync.ts27
-rw-r--r--apps/projects/src/app/lib/i18n/i18n-util.ts31
-rw-r--r--apps/projects/src/app/lib/i18n/nb/index.ts127
-rw-r--r--apps/projects/src/app/lib/stores/locale.ts21
9 files changed, 1195 insertions, 0 deletions
diff --git a/apps/projects/src/app/lib/i18n/en/index.ts b/apps/projects/src/app/lib/i18n/en/index.ts
new file mode 100644
index 0000000..9d74481
--- /dev/null
+++ b/apps/projects/src/app/lib/i18n/en/index.ts
@@ -0,0 +1,127 @@
+import type {BaseTranslation} from "../i18n-types";
+
+const en: BaseTranslation = {
+ nav: {
+ home: "Home",
+ data: "Data",
+ settings: "Settings",
+ usermenu: {
+ logout: "Log out",
+ logoutTitle: "Log out of your profile",
+ profile: "Profile",
+ profileTitle: "Administrate your profile",
+ toggleTitle: "Toggle user menu",
+ }
+ },
+ views: {
+ dataTablePaginator: {
+ goToPrevPage: "Go to previous page",
+ goToNextPage: "Go to next page",
+ of: "of",
+ },
+ categoryForm: {
+ name: "Name",
+ color: "Color",
+ defaultLabels: "Default labels",
+ labelsPlaceholder: "Search or create"
+ },
+ settingsCategoriesTile: {
+ deleteAllConfirm: "Are you sure you want to delete this category?\nThis will delete all relating entries!",
+ active: "Active",
+ archived: "Archived",
+ name: "Name",
+ color: "Color",
+ editEntry: "Edit entry",
+ deleteEntry: "Delete entry",
+ noCategories: "No categories",
+ categories: "Categories"
+ },
+ settingsLabelsTile: {
+ deleteAllConfirm: "Are you sure you want to delete this label?\nIt will be removed from all related entries!",
+ active: "Active",
+ archived: "Archived",
+ name: "Name",
+ color: "Color",
+ editEntry: "Edit label",
+ deleteEntry: "Delete label",
+ noLabels: "No labels",
+ labels: "Labels"
+ },
+ entryForm: {
+ entryUpdateError: "An error occured while updating the entry, try again soon.",
+ entryCreateError: "An error occured while creating the entry, try again soon.",
+ errDescriptionReq: "Description is required",
+ reset: "Reset",
+ description: "Description",
+ save: "Save",
+ create: "Create",
+ category: {
+ category: "Category",
+ placeholder: "Search or create",
+ noResults: "No categories available (Create a new one by searching for it)",
+ errisRequired: "Category is required",
+ _logReset: "Reset category section"
+ },
+ labels: {
+ placeholder: "Search or create",
+ noResults: "No labels available (Create a new one by searching for it)",
+ labels: "Labels",
+ _logReset: "Reset labels section"
+ },
+ dateTime: {
+ errDateIsRequired: "Date is required",
+ errFromIsRequired: "From is required",
+ errFromAfterTo: "From can not be after To",
+ errFromEqTo: "From and To can not be equal",
+ errToIsRequired: "To is required",
+ errToBeforeFrom: "To can not be before From",
+ from: "From",
+ to: "To",
+ date: "Date",
+ _logReset: "Reset date time section"
+ }
+ }
+ },
+ data: {
+ durationSummary: "Showing {entryCountString:string}, totalling in {totalHourMin:string}",
+ hourSingleChar: "h",
+ minSingleChar: "m",
+ entry: "entry",
+ entries: "entries",
+ confirmDeleteEntry: "Are you sure you want to delete this entry?",
+ editEntry: "Edit entry",
+ date: "Date",
+ from: "From",
+ duration: "Duration",
+ category: "Category",
+ description: "Description",
+ loading: "Loading",
+ noEntries: "No entries",
+ to: "to",
+ use: "Use",
+ },
+ home: {
+ hourSingleChar: "h",
+ minSingleChar: "m",
+ confirmDeleteEntry: "Are you sure you want to delete this entry?",
+ newEntry: "New entry",
+ editEntry: "Edit entry",
+ deleteEntry: "Delete entry",
+ loggedTimeToday: "Logged time today",
+ currentTime: "Current time",
+ loading: "Loading",
+ stopwatch: "Stopwatch",
+ todayEntries: "Today's entries",
+ noEntriesToday: "No entries today",
+ refreshTodayEntries: "Refresh today's entries",
+ category: "Category",
+ timespan: "Timespan",
+ },
+ messages: {
+ pageNotFound: "Page not found",
+ goToFrontpage: "Go to frontpage",
+ noInternet: "It seems like your device does not have a internet connection, please check your connection."
+ }
+};
+
+export default en;
diff --git a/apps/projects/src/app/lib/i18n/formatters.ts b/apps/projects/src/app/lib/i18n/formatters.ts
new file mode 100644
index 0000000..78734f9
--- /dev/null
+++ b/apps/projects/src/app/lib/i18n/formatters.ts
@@ -0,0 +1,11 @@
+import type { FormattersInitializer } from 'typesafe-i18n'
+import type { Locales, Formatters } from './i18n-types'
+
+export const initFormatters: FormattersInitializer<Locales, Formatters> = (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<Locales, Translations, TranslationFunctions, Formatters>(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<Translations>) =>
+ loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary }
+
+export const loadLocaleAsync = async (locale: Locales): Promise<void> => {
+ updateDictionary(
+ locale,
+ (await localeTranslationLoaders[locale]()).default as unknown as Translations
+ )
+ loadFormatters(locale)
+}
+
+export const loadAllLocalesAsync = (): Promise<void[]> => 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<Locales, Translations>
+
+export const loadedFormatters = {} as Record<Locales, Formatters>
+
+export const i18nString = (locale: Locales) => initI18nString<Locales, Formatters>(locale, loadedFormatters[locale])
+
+export const i18nObject = (locale: Locales) =>
+ initI18nObject<Locales, Translations, TranslationFunctions, Formatters>(
+ locale,
+ loadedLocales[locale],
+ loadedFormatters[locale]
+ )
+
+export const i18n = () => initI18n<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters)
+
+export const detectLocale = (...detectors: LocaleDetector[]) => detectLocaleFn<Locales>(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<Locales>((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());
+});