summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2022-06-07 01:33:52 +0200
committerivarlovlie <git@ivarlovlie.no>2022-06-07 01:34:48 +0200
commita8b44b09a404aee477e735501b828d1b14aad311 (patch)
tree41b8e21f7484a3f93867a137826da255081fb0ab /apps
parent591f1c5fd81bd2b92e91a90847ea06438211078d (diff)
downloadgreatoffice-a8b44b09a404aee477e735501b828d1b14aad311.tar.xz
greatoffice-a8b44b09a404aee477e735501b828d1b14aad311.zip
feat: Add inital translation support
Diffstat (limited to 'apps')
-rw-r--r--apps/frontpage/src/routes/__layout.svelte9
-rw-r--r--apps/portal/src/app/pages/_layout.svelte12
-rw-r--r--apps/portal/src/app/pages/_layout@loggedin.svelte11
-rw-r--r--apps/portal/src/app/pages/home.svelte36
-rw-r--r--apps/portal/src/app/pages/login.svelte2
-rw-r--r--apps/portal/src/app/pages/sign-up.svelte3
-rw-r--r--apps/projects/src/.typesafe-i18n.json5
-rw-r--r--apps/projects/src/app/index.scss1
-rw-r--r--apps/projects/src/app/index.svelte117
-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
-rw-r--r--apps/projects/src/app/pages/_layout.svelte32
-rw-r--r--apps/projects/src/app/pages/data.svelte34
-rw-r--r--apps/projects/src/app/pages/home.svelte27
-rw-r--r--apps/projects/src/app/pages/not-found.svelte5
-rw-r--r--apps/projects/src/app/pages/views/category-form/index.svelte12
-rw-r--r--apps/projects/src/app/pages/views/data-table-paginator.svelte30
-rw-r--r--apps/projects/src/app/pages/views/entry-form/index.svelte13
-rw-r--r--apps/projects/src/app/pages/views/entry-form/sections/category.svelte11
-rw-r--r--apps/projects/src/app/pages/views/entry-form/sections/date-time.svelte233
-rw-r--r--apps/projects/src/app/pages/views/entry-form/sections/labels.svelte9
-rw-r--r--apps/projects/src/app/pages/views/settings-categories-tile.svelte21
-rw-r--r--apps/projects/src/app/pages/views/settings-labels-tile.svelte19
-rw-r--r--apps/projects/src/package.json8
-rw-r--r--apps/projects/src/pnpm-lock.yaml500
-rw-r--r--apps/web-shared/package.json7
-rw-r--r--apps/web-shared/pnpm-lock.yaml714
-rw-r--r--apps/web-shared/src/components/blowout-toolbelt.svelte60
-rw-r--r--apps/web-shared/src/components/link-card.svelte4
-rw-r--r--apps/web-shared/src/components/locale-switcher-icon.svelte16
-rw-r--r--apps/web-shared/src/components/locale-switcher.svelte64
-rw-r--r--apps/web-shared/src/components/theme-switcher-icon.svelte248
-rw-r--r--apps/web-shared/src/components/theme-switcher.svelte248
-rw-r--r--apps/web-shared/src/lib/configuration.ts3
-rw-r--r--apps/web-shared/src/lib/helpers.ts10
42 files changed, 2468 insertions, 1251 deletions
diff --git a/apps/frontpage/src/routes/__layout.svelte b/apps/frontpage/src/routes/__layout.svelte
index ea37fe7..aa70e51 100644
--- a/apps/frontpage/src/routes/__layout.svelte
+++ b/apps/frontpage/src/routes/__layout.svelte
@@ -1,6 +1,6 @@
<script>
import {portal_base} from "$shared/lib/configuration";
- import ThemeSwitcher from "$shared/components/theme-switcher.svelte";
+ import BlowoutToolbelt from "$shared/components/blowout-toolbelt.svelte";
import "./app.scss";
import {afterNavigate} from "$app/navigation";
import {page} from "$app/stores";
@@ -22,7 +22,7 @@
background: hsla(var(--color-contrast-higher-h), var(--color-contrast-higher-s), var(--color-contrast-higher-l), 0.075)
}
</style>
-
+<BlowoutToolbelt/>
<div class="flex@sm">
<aside class="sidebar {showSidebar ? 'sidebar--is-visible': ''}">
<div class="sidebar__panel">
@@ -74,12 +74,7 @@
</div>
</div>
</aside>
-
<main class="position-relative padding-sm z-index-1 flex-grow">
<slot></slot>
</main>
-
- <div class="position-fixed right-0 top-0 margin-md z-index-2">
- <ThemeSwitcher/>
- </div>
</div>
diff --git a/apps/portal/src/app/pages/_layout.svelte b/apps/portal/src/app/pages/_layout.svelte
index 7cb7b2a..8c75cb9 100644
--- a/apps/portal/src/app/pages/_layout.svelte
+++ b/apps/portal/src/app/pages/_layout.svelte
@@ -1,6 +1,5 @@
<script>
- import ThemeSwitcher from "$shared/components/theme-switcher.svelte";
- import {frontpage_base} from "$shared/lib/configuration";
+ import BlowoutToolbelt from "$shared/components/blowout-toolbelt.svelte";
</script>
<style>
@@ -27,18 +26,15 @@
height: auto;
}
</style>
-
+<BlowoutToolbelt/>
+
<main class="container-fluid padding-x-xs padding-x-xxl@xs padding-y-md padding-y-lg@md max-width-sm">
- <a href="{frontpage_base()}" class="block margin-bottom-xs">Go to {frontpage_base()}</a>
<div class="z-index-2 position-relative">
<slot/>
</div>
-
- <div class="position-fixed right-0 top-0 margin-md z-index-2">
- <ThemeSwitcher/>
- </div>
<figure id="decoration"
+ class="z-index-1"
aria-hidden="true">
<svg class="color-contrast-higher opacity-10%"
viewBox="0 0 1920 450"
diff --git a/apps/portal/src/app/pages/_layout@loggedin.svelte b/apps/portal/src/app/pages/_layout@loggedin.svelte
index eb092a1..ea56f73 100644
--- a/apps/portal/src/app/pages/_layout@loggedin.svelte
+++ b/apps/portal/src/app/pages/_layout@loggedin.svelte
@@ -1,5 +1,5 @@
<script>
- import ThemeSwitcher from "$shared/components/theme-switcher.svelte";
+ import BlowoutToolbelt from "$shared/components/blowout-toolbelt.svelte";
import UserMenu from "$app/components/user-menu.svelte";
import {get_session_data} from "$shared/lib/session";
@@ -30,18 +30,19 @@
height: auto;
}
</style>
-
+
+<BlowoutToolbelt/>
<main class="container max-width-xl padding-x-xs padding-x-xxl@xs padding-y-md padding-y-lg@md">
<div class="z-index-2 position-relative">
<slot/>
</div>
-
+
<div class="flex flex-row gap-xs position-fixed right-0 top-0 margin-md z-index-2">
- <UserMenu name="{session.profile?.username}"/>
- <ThemeSwitcher/>
+ <UserMenu name="{session?.profile?.username}"/>
</div>
<figure id="decoration"
+ class="z-index-1"
aria-hidden="true">
<svg class="color-contrast-higher opacity-10%"
viewBox="0 0 1920 450"
diff --git a/apps/portal/src/app/pages/home.svelte b/apps/portal/src/app/pages/home.svelte
index b9b9829..b1859d2 100644
--- a/apps/portal/src/app/pages/home.svelte
+++ b/apps/portal/src/app/pages/home.svelte
@@ -4,7 +4,7 @@
import {push} from "svelte-spa-router";
import Layout from "./_layout@loggedin.svelte";
import LinkCard from "$shared/components/link-card.svelte";
- import {UserIcon, UsersIcon, WatchIcon} from "svelte-feather-icons";
+ import {UserIcon, UsersIcon, WatchIcon, SendIcon, ListIcon} from "svelte-feather-icons";
let showUsers = true;
const session = get_session_data();
@@ -21,7 +21,7 @@
<div class="grid-auto-md gap-sm">
<LinkCard name="Projects"
description="The home for your projects"
- text="Open"
+ text="Open in a new tab"
target="_blank"
title="Open Projects in a new tab"
href="{projects_base()}">
@@ -33,13 +33,43 @@
</div>
</figure>
</LinkCard>
+ <LinkCard name="Tickets"
+ description="The home for your tickets"
+ class="c-disabled user-select-none"
+ text="Coming soon"
+ target="_blank"
+ title="Open Tickets in a new tab"
+ href="{projects_base()}">
+ <figure slot="icon">
+ <div class="bg-primary bg-opacity-10% padding-xs border-left border-primary border-2">
+ <SendIcon size="42"
+ class="color-primary"
+ strokeWidth="1.2"/>
+ </div>
+ </figure>
+ </LinkCard>
+ <LinkCard name="Todo"
+ description="The home for your todos"
+ class="c-disabled user-select-none"
+ text="Coming soon"
+ target="_blank"
+ title="Open Todo in a new tab"
+ href="{projects_base()}">
+ <figure slot="icon">
+ <div class="bg-primary bg-opacity-10% padding-xs border-left border-primary border-2">
+ <ListIcon size="42"
+ class="color-primary"
+ strokeWidth="1.2"/>
+ </div>
+ </figure>
+ </LinkCard>
</div>
</div>
<div class="row">
<h2 class="margin-bottom-xs">Manage</h2>
<div class="grid-auto-md gap-sm">
<LinkCard name="Profile"
- description="Manage your profile information"
+ description="Manage your profile"
text="Open"
title="Go to your profile management page"
on:click={() => push("/profile")}>
diff --git a/apps/portal/src/app/pages/login.svelte b/apps/portal/src/app/pages/login.svelte
index b842463..900c5ac 100644
--- a/apps/portal/src/app/pages/login.svelte
+++ b/apps/portal/src/app/pages/login.svelte
@@ -14,6 +14,7 @@
values: {
username: "",
password: "",
+ persist: true
},
alert: {
title: "",
@@ -92,6 +93,7 @@
</script>
<Layout>
+ <a href="{frontpage_base()}" class="block margin-bottom-xs">Go to {frontpage_base()}</a>
<Tile>
<form on:submit|preventDefault={loginForm.submit_form}
class="max-width-xxs">
diff --git a/apps/portal/src/app/pages/sign-up.svelte b/apps/portal/src/app/pages/sign-up.svelte
index 509d33a..3bcab6d 100644
--- a/apps/portal/src/app/pages/sign-up.svelte
+++ b/apps/portal/src/app/pages/sign-up.svelte
@@ -1,5 +1,6 @@
<script>
import {create_account} from "$shared/lib/api/user";
+ import {frontpage_base} from "$shared/lib/configuration";
import {is_email} from "$shared/lib/helpers";
import Alert from "$shared/components/alert.svelte";
import Button from "$shared/components/button.svelte";
@@ -79,6 +80,8 @@
</script>
<Layout>
+ <a href="{frontpage_base()}"
+ class="block margin-bottom-xs">Go to {frontpage_base()}</a>
<Tile>
<form on:submit|preventDefault={signupForm.submit_form}
class="max-width-xxs">
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 @@
<svelte:options immutable={true}/>
<svelte:window bind:online={online}/>
-<script>
- import {logout_user} from "$app/lib/services/user-service";
- import Router from "svelte-spa-router";
- import {wrap} from "svelte-spa-router/wrap";
- import {QueryClient, QueryClientProvider} from "@sveltestack/svelte-query";
- import {is_active} from "$shared/lib/session";
- import UiWorkbench from "$app/pages/ui-workbench.svelte";
- import NotFound from "$app/pages/not-found.svelte";
- import Home from "$app/pages/home.svelte";
- import Settings from "$app/pages/settings.svelte";
- import Data from "$app/pages/data.svelte";
- import PreHeader from "$shared/components/pre-header.svelte";
+<script lang="ts">
+ import {logout_user} from "$app/lib/services/user-service";
+ import {currentLocale, preffered_or_default} from "$app/lib/stores/locale";
+ import {CookieNames} from "$shared/lib/configuration";
+ import {get_cookie} from "$shared/lib/helpers";
+ import {Temporal} from "@js-temporal/polyfill";
+ import {onMount} from "svelte";
+ import Router from "svelte-spa-router";
+ import {wrap} from "svelte-spa-router/wrap";
+ import {QueryClient, QueryClientProvider} from "@sveltestack/svelte-query";
+ import {is_active} from "$shared/lib/session";
+ import UiWorkbench from "$app/pages/ui-workbench.svelte";
+ import NotFound from "$app/pages/not-found.svelte";
+ import Home from "$app/pages/home.svelte";
+ import Settings from "$app/pages/settings.svelte";
+ import Data from "$app/pages/data.svelte";
+ import PreHeader from "$shared/components/pre-header.svelte";
+ import {setLocale} from "$app/lib/i18n/i18n-svelte";
+ import {loadLocaleAsync} from "$app/lib/i18n/i18n-util.async";
+ import {i18nObject} from "$app/lib/i18n/i18n-util";
- let online = true;
+ let online = true;
+ let notOnlineText;
+ let LL;
- async function user_is_logged_in() {
- if (!await is_active()) {
- await logout_user("expired");
- }
- return true;
- }
+ console.log("Projects Startup Report", {
+ prefferedLocale: navigator.language,
+ timeZone: Temporal.Now.timeZone().id,
+ go_theme: get_cookie(CookieNames.theme),
+ go_locale: get_cookie(CookieNames.locale),
+ prefersColorScheme: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
+ });
- const queryClient = new QueryClient();
-
- const routes = {
- "/home": wrap({
- component: Home,
- conditions: [user_is_logged_in],
- }),
- "/": wrap({
- component: Home,
- conditions: [user_is_logged_in],
- }),
- "/settings": wrap({
- component: Settings,
- conditions: [user_is_logged_in],
- }),
- "/data": wrap({
- component: Data,
- conditions: [user_is_logged_in],
- }),
- "/ui-workbench": UiWorkbench,
- "*": NotFound,
- };
+ currentLocale.subscribe(async locale => {
+ locale = locale === "preffered" ? preffered_or_default() : locale;
+ await loadLocaleAsync(locale);
+ LL = i18nObject(locale);
+ setLocale(locale);
+ });
+
+ onMount(async () => {
+ const locale = $currentLocale === "preffered" ? preffered_or_default() : $currentLocale;
+ await loadLocaleAsync(locale);
+ LL = i18nObject(locale);
+ setLocale(locale);
+ notOnlineText = LL.messages.noInternet();
+ });
+
+ async function user_is_logged_in() {
+ if (!await is_active()) {
+ await logout_user("expired");
+ }
+ return true;
+ }
+
+ const queryClient = new QueryClient();
+
+ const routes = {
+ "/home": wrap({
+ component: Home,
+ conditions: [user_is_logged_in],
+ }),
+ "/": wrap({
+ component: Home,
+ conditions: [user_is_logged_in],
+ }),
+ "/settings": wrap({
+ component: Settings,
+ conditions: [user_is_logged_in],
+ }),
+ "/data": wrap({
+ component: Data,
+ conditions: [user_is_logged_in],
+ }),
+ "/ui-workbench": UiWorkbench,
+ "*": NotFound,
+ };
</script>
-<PreHeader show="{!online}">You seem to be offline, please check your internet connection.</PreHeader>
+<PreHeader show="{!online}">{notOnlineText}</PreHeader>
<QueryClientProvider client={queryClient}>
<Router
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());
+});
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 @@
</script>
<ProfileModal bind:functions={ProfileModalFunctions}/>
+<BlowoutToolbelt on:change={toolbelt_change}/>
<nav class="container max-width-xl@md width-fit-content@md width-100% max-width-none margin-y-xs@md margin-bottom-xs block@md position-relative@md position-absolute bottom-unset@md bottom-0">
<div class="tabs-nav-v2 justify-between">
@@ -28,17 +38,17 @@
<div class="tab-v2">
<a href="/home"
use:link
- class="tabs-nav-v2__item {($location === '/' || $location.startsWith('/home')) ? 'tabs-nav-v2__item--selected' : ''}">Home</a>
+ class="tabs-nav-v2__item {($location === '/' || $location.startsWith('/home')) ? 'tabs-nav-v2__item--selected' : ''}">{$LL.nav.home()}</a>
</div>
<div class="tab-v2">
<a href="/data"
use:link
- class="tabs-nav-v2__item {$location.startsWith('/data') ? 'tabs-nav-v2__item--selected' : ''}">Data</a>
+ class="tabs-nav-v2__item {$location.startsWith('/data') ? 'tabs-nav-v2__item--selected' : ''}">{$LL.nav.data()}</a>
</div>
<div class="tab-v2">
<a href="/settings"
use:link
- class="tabs-nav-v2__item {$location.startsWith('/settings') ? 'tabs-nav-v2__item--selected' : ''}">Settings</a>
+ class="tabs-nav-v2__item {$location.startsWith('/settings') ? 'tabs-nav-v2__item--selected' : ''}">{$LL.nav.settings()}</a>
</div>
</div>
<div class="tab-v2 padding-x-sm">
@@ -51,7 +61,7 @@
icon_width="2rem"
icon_height="2rem"
icon_right_aligned="true"
- title="Toggle user menu"
+ title="{$LL.nav.usermenu.toggleTitle()}"
aria-controls="{userMenuId}"
/>
<Menu bind:show="{showUserMenu}"
@@ -59,14 +69,12 @@
id="{userMenuId}">
<div slot="options">
<MenuItem on:click={() => ProfileModalFunctions.open()}>
- <span title="Administrate your profile">Profile</span>
- </MenuItem>
- <MenuItem on:click={() => switch_theme()}>
- <span title="Change between a dark and light theme">Switch theme</span>
+ <span title="{$LL.nav.usermenu.profileTitle()}">{$LL.nav.usermenu.profile()}</span>
</MenuItem>
<MenuItemSeparator/>
- <MenuItem danger="true" on:click={() => logout_user()}>
- <span title="Log out of your profile">Log out</span>
+ <MenuItem danger="true"
+ on:click={() => logout_user()}>
+ <span title="{$LL.nav.usermenu.logoutTitle()}">{$LL.nav.usermenu.logout()}</span>
</MenuItem>
</div>
</Menu>
diff --git a/apps/projects/src/app/pages/data.svelte b/apps/projects/src/app/pages/data.svelte
index 070b98b..190c641 100644
--- a/apps/projects/src/app/pages/data.svelte
+++ b/apps/projects/src/app/pages/data.svelte
@@ -12,6 +12,7 @@
import {delete_time_entry, get_time_entries, get_time_entry} from "$shared/lib/api/time-entry";
import {seconds_to_hour_minute_string, is_guid, move_focus, unwrap_date_time_from_entry} from "$shared/lib/helpers";
import Button from "$shared/components/button.svelte";
+ import LL from "$app/lib/i18n/i18n-svelte";
let pageCount = 1;
let page = 1;
@@ -41,7 +42,10 @@
function set_duration_summary_string() {
if (entries.length > 0) {
- durationSummary = `Showing ${entries.length} ${entries.length === 1 ? "entry" : "entries"}, totalling in ${seconds_to_hour_minute_string(secondsLogged)}`;
+ durationSummary = $LL.data.durationSummary({
+ entryCountString: `${entries.length} ${entries.length === 1 ? $LL.data.entry() : $LL.data.entries()}`,
+ totalHourMin: seconds_to_hour_minute_string(secondsLogged)
+ });
} else {
durationSummary = "";
}
@@ -61,7 +65,7 @@
date: date_time.start_date,
start: date_time.start_time,
stop: date_time.stop_time,
- durationString: date_time.duration.hours + "h" + date_time.duration.minutes + "m",
+ durationString: date_time.duration.hours + $LL.data.hourSingleChar() + date_time.duration.minutes + $LL.data.minSingleChar(),
seconds: seconds,
category: entry.category,
labels: entry.labels,
@@ -126,7 +130,7 @@
}
async function handle_delete_entry_button_click(e, entryId) {
- if (confirm("Are you sure you want to delete this entry?")) {
+ if (confirm($LL.data.confirmDeleteEntry())) {
const response = await delete_time_entry(entryId);
if (response.ok) {
const indexOfEntry = entries.findIndex((c) => c.id === entryId);
@@ -180,7 +184,7 @@
});
</script>
-<Modal title="Edit entry"
+<Modal title="{$LL.data.editEntry()}"
bind:functions={EditEntryModal}
on:closed={() => EditEntryForm.reset()}>
<EntryForm bind:functions={EditEntryForm}
@@ -216,7 +220,7 @@
{#if currentTimespanFilter === TimeEntryQueryDuration.SPECIFIC_DATE}
<div class="flex items-baseline margin-bottom-xxxxs justify-between">
- <span class="text-sm color-contrast-medium margin-right-xs">Date:</span>
+ <span class="text-sm color-contrast-medium margin-right-xs">{$LL.data.date()}:</span>
<span class="text-sm">
<input type="date"
class="border-none padding-0 color-inherit bg-transparent"
@@ -227,7 +231,7 @@
{#if currentTimespanFilter === TimeEntryQueryDuration.DATE_RANGE}
<div class="flex items-baseline margin-bottom-xxxxs justify-between">
- <span class="text-sm color-contrast-medium margin-right-xs">From:</span>
+ <span class="text-sm color-contrast-medium margin-right-xs">{$LL.data.from()}:</span>
<span class="text-sm">
<input type="date"
class="border-none padding-0 color-inherit bg-transparent"
@@ -236,7 +240,7 @@
</div>
<div class="flex items-baseline margin-bottom-xxxxs justify-between">
- <span class="text-sm color-contrast-medium margin-right-xs">To:</span>
+ <span class="text-sm color-contrast-medium margin-right-xs">{$LL.data.to()}:</span>
<span class="text-sm">
<input type="date"
class="border-none padding-0 color-inherit bg-transparent"
@@ -249,13 +253,13 @@
<Button variant="subtle"
on:click={() => load_entries_with_filter(page)}
class="text-sm"
- text="Save"/>
+ text="{$LL.data.use()}"/>
</div>
</div>
<Layout>
<Tile class="{isLoading ? 'c-disabled loading' : ''}">
- <nav class="s-tabs text-sm">
+ <nav class="s-tabs text-sm hide">
<ul class="s-tabs__list">
<li><span class="s-tabs__link s-tabs__link--current">All (21)</span></li>
<li><span class="s-tabs__link">Published (19)</span></li>
@@ -280,7 +284,7 @@
<TCell type="th"
style="width: 100px">
<div class="flex items-center justify-between">
- <span>Date</span>
+ <span>{$LL.data.date()}</span>
<div class="date_filter_box_el cursor-pointer"
on:click={toggle_date_filter_box}>
<Icon name="{IconNames.funnel}"/>
@@ -291,21 +295,21 @@
<TCell type="th"
style="width: 100px">
<div class="flex items-center">
- <span>Duration</span>
+ <span>{$LL.data.duration()}</span>
</div>
</TCell>
<TCell type="th"
style="width: 100px;">
<div class="flex items-center">
- <span>Category</span>
+ <span>{$LL.data.category()}</span>
</div>
</TCell>
<TCell type="th"
style="width: 300px;">
<div class="flex items-center">
- <span>Description</span>
+ <span>{$LL.data.description()}</span>
</div>
</TCell>
<TCell type="th"
@@ -366,7 +370,7 @@
<TCell type="th"
thScope="row"
colspan="7">
- {isLoading ? "Loading..." : "No entries"}
+ {isLoading ? $LL.data.loading() + "..." : $LL.data.noEntries()}
</TCell>
</TRow>
{/if}
@@ -378,7 +382,7 @@
{#if durationSummary}
<small class={isLoading ? "c-disabled loading" : ""}>{durationSummary}</small>
{:else}
- <small class={isLoading ? "c-disabled loading" : ""}>No entries</small>
+ <small class={isLoading ? "c-disabled loading" : ""}>{$LL.data.noEntries()}</small>
{/if}
</p>
diff --git a/apps/projects/src/app/pages/home.svelte b/apps/projects/src/app/pages/home.svelte
index 84d6728..33bb0d8 100644
--- a/apps/projects/src/app/pages/home.svelte
+++ b/apps/projects/src/app/pages/home.svelte
@@ -1,4 +1,5 @@
<script lang="ts">
+ import LL from "$app/lib/i18n/i18n-svelte";
import {delete_time_entry, get_time_entries, get_time_entry, update_time_entry} from "$shared/lib/api/time-entry";
import {IconNames, QueryKeys} from "$shared/lib/configuration";
import {TimeEntryDto} from "$shared/lib/models/TimeEntryDto";
@@ -18,7 +19,7 @@
let isLoading = false;
let EditEntryForm: any;
let timeEntries = [] as Array<TimeEntryDto>;
- let timeLoggedTodayString = "0h0m";
+ let timeLoggedTodayString = "0" + $LL.home.hourSingleChar() + "0" + $LL.home.minSingleChar();
const queryClient = useQueryClient();
const queryResult = useQuery(QueryKeys.entries, async () => await get_time_entries({
@@ -49,7 +50,7 @@
}
async function on_delete_entry_button_click(event, entryId: string) {
- if (confirm("Are you sure you want to delete this entry?")) {
+ if (confirm($LL.home.confirmDeleteEntry())) {
$delete_entry_mutation.mutate(entryId);
}
}
@@ -88,34 +89,34 @@
<Layout>
<div class="grid gap-md margin-top-xs flex-row@md items-start flex-column-reverse">
<Tile class="col">
- <h3 class="text-md padding-bottom-xxxs">New entry</h3>
+ <h3 class="text-md padding-bottom-xxxs">{$LL.home.newEntry()}</h3>
<EntryFrom bind:functions={EditEntryForm}/>
</Tile>
<div class="col grid gap-sm">
<Tile class="col-6@md col-12">
<p class="text-xxl">{timeLoggedTodayString}</p>
- <p class="text-xs margin-bottom-xxs">Logged time today</p>
+ <p class="text-xs margin-bottom-xxs">{$LL.home.loggedTimeToday()}</p>
<pre class="text-xxl">{currentTime}</pre>
- <p class="text-xs">Current time</p>
+ <p class="text-xs">{$LL.home.currentTime()}</p>
</Tile>
<Tile class="col-6@md col-12">
<Stopwatch on:create={on_create_from_stopwatch}>
<h3 slot="header"
- class="text-md">Stopwatch</h3>
+ class="text-md">{$LL.home.stopwatch()}</h3>
</Stopwatch>
</Tile>
<Tile class="col-12">
- <h3 class="text-md padding-bottom-xxxs">Today's entries</h3>
+ <h3 class="text-md padding-bottom-xxxs">{$LL.home.todayEntries()}</h3>
<div class="max-width-100% overflow-auto">
<Table class="width-100% text-sm">
<THead>
<TCell type="th"
class="text-left">
- <span>Category</span>
+ <span>{$LL.home.category()}</span>
</TCell>
<TCell type="th"
class="text-left">
- <span>Timespan</span>
+ <span>{$LL.home.timespan()}</span>
</TCell>
<TCell type="th"
class="text-right">
@@ -123,7 +124,7 @@
variant="reset"
icon_width="1.2rem"
icon_height="1.2rem"
- title="Refresh today's entries"
+ title="{$LL.home.refreshTodayEntries()}"
on:click={() => queryClient.invalidateQueries(QueryKeys.entries)}/>
</TCell>
</THead>
@@ -148,13 +149,13 @@
icon_width="1.2rem"
icon_height="1.2rem"
on:click={(e) => on_edit_entry_button_click(e, entry.id)}
- title="Edit entry"/>
+ title="{$LL.home.editEntry()}"/>
<Button icon="{IconNames.trash}"
variant="reset"
icon_width="1.2rem"
icon_height="1.2rem"
on:click={(e) => on_delete_entry_button_click(e, entry.id)}
- title="Delete entry"/>
+ title="{$LL.home.deleteEntry()}"/>
</TCell>
</TRow>
{/each}
@@ -163,7 +164,7 @@
<TCell type="th"
thScope="row"
colspan="7">
- {isLoading ? "Loading..." : "No entries today"}
+ {isLoading ? $LL.home.loading() + "..." : $LL.home.noEntriesToday()}
</TCell>
</TRow>
{/if}
diff --git a/apps/projects/src/app/pages/not-found.svelte b/apps/projects/src/app/pages/not-found.svelte
index 46d0d1d..8822e0e 100644
--- a/apps/projects/src/app/pages/not-found.svelte
+++ b/apps/projects/src/app/pages/not-found.svelte
@@ -1,4 +1,5 @@
<script>
+ import LL from "$app/lib/i18n/i18n-svelte";
import {link} from "svelte-spa-router";
</script>
@@ -18,7 +19,7 @@
<main>
<header>404</header>
- <p>Page not found!</p>
+ <p>{$LL.messages.pageNotFound()}</p>
<a use:link
- href="/">Go to front</a>
+ href="/">{$LL.messages.goToFrontpage()}</a>
</main>
diff --git a/apps/projects/src/app/pages/views/category-form/index.svelte b/apps/projects/src/app/pages/views/category-form/index.svelte
index e8c0f94..21024c3 100644
--- a/apps/projects/src/app/pages/views/category-form/index.svelte
+++ b/apps/projects/src/app/pages/views/category-form/index.svelte
@@ -1,9 +1,9 @@
<script lang="ts">
import Alert from "$shared/components/alert.svelte";
import Dropdown from "$shared/components/dropdown.svelte";
- import labels, {reload_labels, create_label_async} from "$app/lib/stores/labels";
+ import labels, {create_label_async} from "$app/lib/stores/labels";
import {generate_random_hex_color} from "$shared/lib/colors";
- import {get} from "svelte/store";
+ import LL from "$app/lib/i18n/i18n-svelte";
let LabelsDropdown;
@@ -106,7 +106,7 @@
<div class="grid gap-x-xs margin-bottom-sm">
<div class="col-10">
<label for="name"
- class="form-label margin-bottom-xxs">Name</label>
+ class="form-label margin-bottom-xxs">{$LL.views.categoryForm.name()}</label>
<input type="text"
class="form-control width-100%"
id="name"
@@ -117,7 +117,7 @@
</div>
<div class="col-2">
<label for="color"
- class="form-label margin-bottom-xxs">Color</label>
+ class="form-label margin-bottom-xxs">{$LL.views.categoryForm.color()}</label>
<input type="color"
class="form-control width-100%"
id="color"
@@ -130,10 +130,10 @@
</div>
<div class="margin-bottom-sm">
<label for="labels"
- class="form-label margin-bottom-xxs">Default labels</label>
+ class="form-label margin-bottom-xxs">{$LL.views.categoryForm.defaultLabels()}</label>
<Dropdown id="labels"
createable={true}
- placeholder="Search or create"
+ placeholder="{$LL.views.categoryForm.labelsPlaceholder()}"
entries={$labels}
multiple={true}
on_create_async={(name) => dough.fields.labels.create({name})}/>
diff --git a/apps/projects/src/app/pages/views/data-table-paginator.svelte b/apps/projects/src/app/pages/views/data-table-paginator.svelte
index 3d9834a..b2649eb 100644
--- a/apps/projects/src/app/pages/views/data-table-paginator.svelte
+++ b/apps/projects/src/app/pages/views/data-table-paginator.svelte
@@ -1,4 +1,5 @@
<script>
+ import LL from "$app/lib/i18n/i18n-svelte";
import {createEventDispatcher, onMount} from "svelte";
import {restrict_input_to_numbers} from "$shared/lib/helpers";
@@ -52,8 +53,8 @@
<button on:click={decrement}
class="reset pagination__item {canDecrement ? '' : 'c-disabled'}">
<svg class="icon icon--xs flip-x"
- viewBox="0 0 16 16"
- ><title>Go to previous page</title>
+ viewBox="0 0 16 16">
+ <title>{$LL.views.dataTablePaginator.goToPrevPage()}</title>
<polyline
points="6 2 12 8 6 14"
fill="none"
@@ -68,26 +69,23 @@
<li>
<span class="pagination__jumper flex items-center">
- <input
- aria-label="Page number"
- class="form-control"
- id="curr-page"
- type="text"
- on:change={handle_change}
- value={page}
+ <input aria-label="Page number"
+ class="form-control"
+ id="curr-page"
+ type="text"
+ on:change={handle_change}
+ value={page}
/>
- <em>of {pageCount}</em>
+ <em>{$LL.views.dataTablePaginator.of()} {pageCount}</em>
</span>
</li>
<li>
- <button
- on:click={increment}
- class="reset pagination__item {canIncrement ? '' : 'c-disabled'}"
- >
+ <button on:click={increment}
+ class="reset pagination__item {canIncrement ? '' : 'c-disabled'}">
<svg class="icon icon--xs"
- viewBox="0 0 16 16"
- ><title>Go to next page</title>
+ viewBox="0 0 16 16">
+ <title>{$LL.views.dataTablePaginator.goToNextPage()}</title>
<polyline
points="6 2 12 8 6 14"
fill="none"
diff --git a/apps/projects/src/app/pages/views/entry-form/index.svelte b/apps/projects/src/app/pages/views/entry-form/index.svelte
index cb974ed..cf3d173 100644
--- a/apps/projects/src/app/pages/views/entry-form/index.svelte
+++ b/apps/projects/src/app/pages/views/entry-form/index.svelte
@@ -1,4 +1,5 @@
<script lang="ts">
+ import LL from "$app/lib/i18n/i18n-svelte";
import {TimeEntryDto} from "$shared/lib/models/TimeEntryDto";
import {Temporal} from "@js-temporal/polyfill";
import {createEventDispatcher, onMount, onDestroy} from "svelte";
@@ -48,7 +49,7 @@
function description_is_valid() {
if (!description) {
- descriptionError = "Description is required";
+ descriptionError = $LL.views.entryForm.errDescriptionReq();
} else {
descriptionError = "";
}
@@ -97,7 +98,7 @@
functions.reset();
dispatch("updated", response.data);
} else {
- formError = "An error occured while updating the entry, try again soon";
+ formError = $LL.views.entryForm.entryUpdateError();
formIsLoading = false;
}
} else {
@@ -106,7 +107,7 @@
functions.reset();
dispatch("created");
} else {
- formError = "An error occured while creating the entry, try again soon";
+ formError = $LL.views.entryForm.entryCreateError();
formIsLoading = false;
}
}
@@ -175,14 +176,14 @@
<div class="margin-bottom-sm">
<Textarea class="width-100%"
id="description"
- label="Description"
+ label="{$LL.views.entryForm.description()}"
errorText="{descriptionError}"
bind:value={description}></Textarea>
</div>
<div class="flex flex-row justify-end gap-x-xs">
{#if entryId}
- <Button text="Reset"
+ <Button text="{$LL.views.entryForm.reset()}"
on:click={() => functions.reset()}
variant="subtle"
/>
@@ -190,7 +191,7 @@
<Button loading={formIsLoading}
type="submit"
variant="primary"
- text={entryId ? "Save" : "Create"}
+ text={entryId ? $LL.views.entryForm.save() : $LL.views.entryForm.create()}
/>
</div>
</form>
diff --git a/apps/projects/src/app/pages/views/entry-form/sections/category.svelte b/apps/projects/src/app/pages/views/entry-form/sections/category.svelte
index aac84be..f7af382 100644
--- a/apps/projects/src/app/pages/views/entry-form/sections/category.svelte
+++ b/apps/projects/src/app/pages/views/entry-form/sections/category.svelte
@@ -3,6 +3,7 @@
import Dropdown from "$shared/components/dropdown.svelte";
import {is_guid, move_focus} from "$shared/lib/helpers";
import categories, {reload_categories, create_category_async} from "$app/lib/stores/categories";
+ import LL from "$app/lib/i18n/i18n-svelte"
let categoriesError = "";
let loading = false;
@@ -12,7 +13,7 @@
function reset() {
DropdownExports.reset();
categoriesError = "";
- console.log("Reset category-part");
+ console.log($LL.views.entryForm.category._logReset());
}
async function on_create({name}) {
@@ -40,7 +41,7 @@
let isValid = true;
const category = get_selected();
if (!is_guid(category?.id)) {
- categoriesError = "Category is required";
+ categoriesError = $LL.views.entryForm.category.errisRequired();
isValid = false;
move_focus(document.getElementById("category-dropdown"));
} else {
@@ -60,15 +61,15 @@
<Dropdown
entries={$categories}
- label="Category"
+ label="{$LL.views.entryForm.category.category()}"
maxlength="50"
createable={true}
- placeholder="Search or create"
+ placeholder="{$LL.views.entryForm.category.placeholder()}"
id="category-dropdown"
loading={loading}
name="category-dropdown"
on_create_async={on_create}
- noResultsText="No categories available (Create a new one by searching for it)"
+ noResultsText="{$LL.views.entryForm.category.noResults()}"
errorText="{categoriesError}"
bind:this={DropdownExports}
/>
diff --git a/apps/projects/src/app/pages/views/entry-form/sections/date-time.svelte b/apps/projects/src/app/pages/views/entry-form/sections/date-time.svelte
index c91e014..47b06e3 100644
--- a/apps/projects/src/app/pages/views/entry-form/sections/date-time.svelte
+++ b/apps/projects/src/app/pages/views/entry-form/sections/date-time.svelte
@@ -1,131 +1,134 @@
<script lang="ts">
- import {Temporal} from "@js-temporal/polyfill";
+ import LL from "$app/lib/i18n/i18n-svelte";
+ import {Temporal} from "@js-temporal/polyfill";
- // TIME
- let fromTimeValue = "";
- let fromTimeError = "";
- let toTimeValue = "";
- let toTimeError = "";
+ // TIME
+ let fromTimeValue = "";
+ let fromTimeError = "";
+ let toTimeValue = "";
+ let toTimeError = "";
- function handle_from_time_changed(e) {
- fromTimeValue = e.target.value;
- if (fromTimeValue) {
- fromTimeError = "";
- }
- }
+ function handle_from_time_changed(e) {
+ fromTimeValue = e.target.value;
+ if (fromTimeValue) {
+ fromTimeError = "";
+ }
+ }
- function handle_to_time_changed(e) {
- toTimeValue = e.target.value;
- if (toTimeValue) {
- toTimeError = "";
- }
- }
+ function handle_to_time_changed(e) {
+ toTimeValue = e.target.value;
+ if (toTimeValue) {
+ toTimeError = "";
+ }
+ }
- // DATE
- let date = Temporal.Now.plainDateTimeISO().toString().substring(0, 10);
- let dateError = "";
+ // DATE
+ let date = Temporal.Now.plainDateTimeISO().toString().substring(0, 10);
+ let dateError = "";
- function is_valid() {
- let isValid = true;
- let focusIsSet = false;
- if (!date) {
- dateError = "Date is required";
- isValid = false;
- if (!focusIsSet) {
- document.getElementById("date")?.focus();
- focusIsSet = true;
- }
- } else {
- dateError = "";
- }
+ function is_valid() {
+ let isValid = true;
+ let focusIsSet = false;
+ if (!date) {
+ dateError = $LL.views.entryForm.dateTime.errDateIsRequired();
+ isValid = false;
+ if (!focusIsSet) {
+ document.getElementById("date")?.focus();
+ focusIsSet = true;
+ }
+ } else {
+ dateError = "";
+ }
- if (!fromTimeValue) {
- fromTimeError = "From is required";
- isValid = false;
- if (!focusIsSet) {
- document.getElementById("from")?.focus();
- focusIsSet = true;
- }
- } else if (toTimeValue && fromTimeValue > toTimeValue) {
- fromTimeError = "From can not be after To";
- isValid = false;
- if (!focusIsSet) {
- document.getElementById("from")?.focus();
- focusIsSet = true;
- }
- } else if (fromTimeValue === toTimeValue) {
- fromTimeError = "From and To can not be equal";
- isValid = false;
- if (!focusIsSet) {
- document.getElementById("from")?.focus();
- focusIsSet = true;
- }
- } else {
- fromTimeError = "";
- }
+ if (!fromTimeValue) {
+ fromTimeError = $LL.views.entryForm.dateTime.errFromIsRequired();
+ isValid = false;
+ if (!focusIsSet) {
+ document.getElementById("from")?.focus();
+ focusIsSet = true;
+ }
+ } else if (toTimeValue && fromTimeValue > toTimeValue) {
+ fromTimeError = $LL.views.entryForm.dateTime.errFromAfterTo();
+ isValid = false;
+ if (!focusIsSet) {
+ document.getElementById("from")?.focus();
+ focusIsSet = true;
+ }
+ } else if (fromTimeValue === toTimeValue) {
+ fromTimeError = $LL.views.entryForm.dateTime.errFromEqTo();
- if (!toTimeValue) {
- toTimeError = "To is required";
- isValid = false;
- if (!focusIsSet) {
- document.getElementById("to")?.focus();
- focusIsSet = true;
- }
- } else if (fromTimeValue && toTimeValue < fromTimeValue) {
- toTimeError = "To can not be before From";
- isValid = false;
- if (!focusIsSet) {
- document.getElementById("to")?.focus();
- focusIsSet = true;
- }
- } else {
- toTimeError = "";
- }
+ isValid = false;
+ if (!focusIsSet) {
+ document.getElementById("from")?.focus();
+ focusIsSet = true;
+ }
+ } else {
+ fromTimeError = "";
+ }
- return isValid;
- }
+ if (!toTimeValue) {
+ toTimeError = $LL.views.entryForm.dateTime.errToIsRequired();
+ isValid = false;
+ if (!focusIsSet) {
+ document.getElementById("to")?.focus();
+ focusIsSet = true;
+ }
+ } else if (fromTimeValue && toTimeValue < fromTimeValue) {
+ toTimeError = $LL.views.entryForm.dateTime.errToBeforeFrom();
+ isValid = false;
+ if (!focusIsSet) {
+ document.getElementById("to")?.focus();
+ focusIsSet = true;
+ }
+ } else {
+ toTimeError = "";
+ }
- export const functions = {
- get_from_time_value() {
- return fromTimeValue;
- },
- get_to_time_value() {
- return toTimeValue;
- },
- get_date() {
- return date;
- },
- is_valid,
- reset(focusDate = false) {
- fromTimeValue = "";
- toTimeValue = "";
- if (focusDate) {
- document.getElementById("date")?.focus();
- }
- },
- set_times(value) {
- console.log(value);
- fromTimeValue = value.from.toString().substring(0, 5);
- toTimeValue = value.to.toString().substring(0, 5);
- },
- set_date(new_date: Temporal.PlainDate) {
- date = new_date.toString();
- },
- set_values(values) {
- const currentTimeZone = Temporal.Now.timeZone().id;
- const startDate = Temporal.Instant.from(values.start);
- const stopDate = Temporal.Instant.from(values.stop);
- fromTimeValue = startDate.toZonedDateTimeISO(currentTimeZone).toPlainTime().toString().substring(0, 5);
- toTimeValue = stopDate.toZonedDateTimeISO(currentTimeZone).toPlainTime().toString().substring(0, 5);
- date = startDate.toZonedDateTimeISO(currentTimeZone).toPlainDate().toString();
- }
- };
+ return isValid;
+ }
+
+ export const functions = {
+ get_from_time_value() {
+ return fromTimeValue;
+ },
+ get_to_time_value() {
+ return toTimeValue;
+ },
+ get_date() {
+ return date;
+ },
+ is_valid,
+ reset(focusDate = false) {
+ fromTimeValue = "";
+ toTimeValue = "";
+ if (focusDate) {
+ document.getElementById("date")?.focus();
+ }
+ console.log($LL.views.entryForm.dateTime._logReset());
+ },
+ set_times(value) {
+ console.log(value);
+ fromTimeValue = value.from.toString().substring(0, 5);
+ toTimeValue = value.to.toString().substring(0, 5);
+ },
+ set_date(new_date: Temporal.PlainDate) {
+ date = new_date.toString();
+ },
+ set_values(values) {
+ const currentTimeZone = Temporal.Now.timeZone().id;
+ const startDate = Temporal.Instant.from(values.start);
+ const stopDate = Temporal.Instant.from(values.stop);
+ fromTimeValue = startDate.toZonedDateTimeISO(currentTimeZone).toPlainTime().toString().substring(0, 5);
+ toTimeValue = stopDate.toZonedDateTimeISO(currentTimeZone).toPlainTime().toString().substring(0, 5);
+ date = startDate.toZonedDateTimeISO(currentTimeZone).toPlainDate().toString();
+ }
+ };
</script>
<div class="grid gap-xs">
<div class="col-4">
<label for="date"
- class="form-label margin-bottom-xxs">Date</label>
+ class="form-label margin-bottom-xxs">{$LL.views.entryForm.dateTime.date()}</label>
<input type="date"
id="date"
class="form-control width-100%"
@@ -136,7 +139,7 @@
</div>
<div class="col-4">
<label for="from"
- class="form-label margin-bottom-xxs">From</label>
+ class="form-label margin-bottom-xxs">{$LL.views.entryForm.dateTime.from()}</label>
<input id="from"
class="form-control width-100%"
pattern="[0-9][0-9]:[0-9][0-9]"
@@ -150,7 +153,7 @@
</div>
<div class="col-4">
<label for="to"
- class="form-label margin-bottom-xxs">To</label>
+ class="form-label margin-bottom-xxs">{$LL.views.entryForm.dateTime.to()}</label>
<input id="to"
class="form-control width-100%"
pattern="[0-9][0-9]:[0-9][0-9]"
diff --git a/apps/projects/src/app/pages/views/entry-form/sections/labels.svelte b/apps/projects/src/app/pages/views/entry-form/sections/labels.svelte
index f0853cc..a6f324b 100644
--- a/apps/projects/src/app/pages/views/entry-form/sections/labels.svelte
+++ b/apps/projects/src/app/pages/views/entry-form/sections/labels.svelte
@@ -1,4 +1,5 @@
<script>
+ import LL from "$app/lib/i18n/i18n-svelte";
import {generate_random_hex_color} from "$shared/lib/colors";
import labels, {reload_labels, create_label_async} from "$app/lib/stores/labels";
import Dropdown from "$shared/components/dropdown.svelte";
@@ -9,7 +10,7 @@
function reset() {
DropdownExports.reset();
- console.log("Reset labels-part");
+ console.log($LL.views.entryForm.labels._logReset());
}
function get_selected() {
@@ -50,15 +51,15 @@
<Dropdown
entries={$labels}
- label="Labels"
+ label="{$LL.views.entryForm.labels.labels()}"
maxlength="50"
createable={true}
- placeholder="Search or create"
+ placeholder="{$LL.views.entryForm.labels.placeholder()}"
multiple="{true}"
id="labels-search"
name="labels-search"
on_create_async={on_create}
- noResultsText="No labels available (Create a new one by searching for it)"
+ noResultsText="{$LL.views.entryForm.labels.placeholder()}"
errorText="{labelsError}"
bind:this={DropdownExports}
{loading}
diff --git a/apps/projects/src/app/pages/views/settings-categories-tile.svelte b/apps/projects/src/app/pages/views/settings-categories-tile.svelte
index 890609a..8d2480f 100644
--- a/apps/projects/src/app/pages/views/settings-categories-tile.svelte
+++ b/apps/projects/src/app/pages/views/settings-categories-tile.svelte
@@ -8,6 +8,7 @@
import Button from "$shared/components/button.svelte";
import Tile from "$shared/components/tile.svelte";
import {Table, THead, TBody, TCell, TRow} from "$shared/components/table";
+ import LL from "$app/lib/i18n/i18n-svelte";
let is_loading = true;
let categories = [];
@@ -38,9 +39,7 @@
if (
row &&
row.dataset.id &&
- confirm(
- "Are you sure you want to delete this category?\nThis will delete all relating entries!"
- )
+ confirm($LL.views.settingsCategoriesTile.deleteAllConfirm())
) {
const response = await delete_time_category(row.dataset.id);
if (response.ok) {
@@ -56,14 +55,14 @@
</script>
<Tile class="col-6@md col-12 {is_loading ? 'c-disabled loading' : ''}">
- <h2 class="margin-bottom-xxs">Categories</h2>
+ <h2 class="margin-bottom-xxs">{$LL.views.settingsCategoriesTile.categories()}</h2>
{#if active_categories.length > 0 && archived_categories.length > 0}
<nav class="s-tabs text-sm">
<ul class="s-tabs__list">
<li><a class="s-tabs__link s-tabs__link--current"
- href="#0">Active ({active_categories.length})</a></li>
+ href="#0">{$LL.views.settingsCategoriesTile.active()} ({active_categories.length})</a></li>
<li><a class="s-tabs__link"
- href="#0">Archived ({archived_categories.length})</a></li>
+ href="#0">{$LL.views.settingsCategoriesTile.archived()} ({archived_categories.length})</a></li>
</ul>
</nav>
{/if}
@@ -72,11 +71,11 @@
<THead class="text-left">
<TCell type="th"
thScope="col">
- Name
+ {$LL.views.settingsCategoriesTile.name()}
</TCell>
<TCell type="th"
thScope="col">
- Color
+ {$LL.views.settingsCategoriesTile.color()}
</TCell>
<TCell type="th"
thScope="col"
@@ -102,13 +101,13 @@
class="hide"
icon_height="1.2rem"
on:click={handle_edit_category_click}
- title="Edit entry"/>
+ title="{$LL.views.settingsCategoriesTile.editEntry()}"/>
<Button icon="{IconNames.trash}"
variant="reset"
icon_width="1.2rem"
icon_height="1.2rem"
on:click={handle_delete_category_click}
- title="Delete entry"/>
+ title="{$LL.views.settingsCategoriesTile.deleteEntry()}"/>
</TCell>
</TRow>
@@ -117,7 +116,7 @@
<TRow>
<TCell type="th"
thScope="3">
- No categories
+ {$LL.views.settingsCategoriesTile.noCategories()}
</TCell>
</TRow>
{/if}
diff --git a/apps/projects/src/app/pages/views/settings-labels-tile.svelte b/apps/projects/src/app/pages/views/settings-labels-tile.svelte
index f59e233..59b5e30 100644
--- a/apps/projects/src/app/pages/views/settings-labels-tile.svelte
+++ b/apps/projects/src/app/pages/views/settings-labels-tile.svelte
@@ -5,6 +5,7 @@
import Button from "$shared/components/button.svelte";
import Tile from "$shared/components/tile.svelte";
import {Table, THead, TBody, TCell, TRow} from "$shared/components/table";
+ import LL from "$app/lib/i18n/i18n-svelte";
let is_loading = true;
@@ -25,9 +26,7 @@
if (
row &&
row.dataset.id &&
- confirm(
- "Are you sure you want to delete this label?\nIt will be removed from all related entries!"
- )
+ confirm($LL.views.settingsLabelsTile.deleteAllConfirm())
) {
await delete_label_async({id: row.dataset.id});
row.classList.add("d-none");
@@ -40,14 +39,14 @@
</script>
<Tile class="col-6@md col-12 {is_loading ? 'c-disabled loading' : ''}">
- <h2 class="margin-bottom-xxs">Labels</h2>
+ <h2 class="margin-bottom-xxs">{$LL.views.settingsLabelsTile.labels()}</h2>
{#if active_labels.length > 0 && archived_labels.length > 0}
<nav class="s-tabs text-sm">
<ul class="s-tabs__list">
<li><a class="s-tabs__link s-tabs__link--current"
- href="#0">Active ({active_labels.length})</a></li>
+ href="#0">{$LL.views.settingsLabelsTile.active()} ({active_labels.length})</a></li>
<li><a class="s-tabs__link"
- href="#0">Archived ({archived_labels.length})</a></li>
+ href="#0">{$LL.views.settingsLabelsTile.archived()} ({archived_labels.length})</a></li>
</ul>
</nav>
{/if}
@@ -56,11 +55,11 @@
<THead class="text-left">
<TCell type="th"
thScope="row">
- Name
+ {$LL.views.settingsLabelsTile.name()}
</TCell>
<TCell type="th"
thScope="row">
- Color
+ {$LL.views.settingsLabelsTile.color()}
</TCell>
<TCell type="th"
thScope="row"
@@ -87,13 +86,13 @@
class="hide"
icon_height="1.2rem"
on:click={handle_edit_label_click}
- title="Edit entry"/>
+ title="{$LL.views.settingsLabelsTile.editEntry()}"/>
<Button icon="{IconNames.trash}"
variant="reset"
icon_width="1.2rem"
icon_height="1.2rem"
on:click={handle_delete_label_click}
- title="Delete entry"/>
+ title="{$LL.views.settingsLabelsTile.deleteEntry()}"/>
</TCell>
</TRow>
{/each}
diff --git a/apps/projects/src/package.json b/apps/projects/src/package.json
index 636df49..d7b41a8 100644
--- a/apps/projects/src/package.json
+++ b/apps/projects/src/package.json
@@ -3,13 +3,16 @@
"version": "0.0.1",
"private": "true",
"scripts": {
- "dev": "vite",
+ "dev": "npm-run-all --parallel vite typesafe-i18n",
+ "vite": "vite",
+ "typesafe-i18n": "typesafe-i18n",
"build": "vite build"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "1.0.0-next.43",
"@sveltestack/svelte-query": "^1.6.0",
"broadcast-channel": "^4.13.0",
+ "npm-run-all": "^4.1.5",
"sass": "^1.51.0",
"svelte": "^3.48.0",
"svelte-preprocess": "^4.10.6",
@@ -19,6 +22,7 @@
},
"dependencies": {
"@js-temporal/polyfill": "^0.4.1",
- "fuzzysort": "^1.9.0"
+ "fuzzysort": "^1.9.0",
+ "typesafe-i18n": "^5.5.2"
}
}
diff --git a/apps/projects/src/pnpm-lock.yaml b/apps/projects/src/pnpm-lock.yaml
index aae0331..094c1be 100644
--- a/apps/projects/src/pnpm-lock.yaml
+++ b/apps/projects/src/pnpm-lock.yaml
@@ -6,21 +6,25 @@ specifiers:
'@sveltestack/svelte-query': ^1.6.0
broadcast-channel: ^4.13.0
fuzzysort: ^1.9.0
+ npm-run-all: ^4.1.5
sass: ^1.51.0
svelte: ^3.48.0
svelte-preprocess: ^4.10.6
svelte-spa-router: ^3.2.0
+ typesafe-i18n: ^5.5.2
typescript: 4.6.4
vite: ^2.9.8
dependencies:
'@js-temporal/polyfill': 0.4.1
fuzzysort: 1.9.0
+ typesafe-i18n: 5.5.2_typescript@4.6.4
devDependencies:
'@sveltejs/vite-plugin-svelte': 1.0.0-next.43_svelte@3.48.0+vite@2.9.8
'@sveltestack/svelte-query': 1.6.0_broadcast-channel@4.13.0
broadcast-channel: 4.13.0
+ npm-run-all: 4.1.5
sass: 1.51.0
svelte: 3.48.0
svelte-preprocess: 4.10.6_24ezlekk4ocevlsjgs2qnqmjum
@@ -101,6 +105,13 @@ packages:
'@types/node': 17.0.31
dev: true
+ /ansi-styles/3.2.1:
+ resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
+ engines: {node: '>=4'}
+ dependencies:
+ color-convert: 1.9.3
+ dev: true
+
/anymatch/3.1.2:
resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
engines: {node: '>= 8'}
@@ -148,6 +159,22 @@ packages:
resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=}
dev: true
+ /call-bind/1.0.2:
+ resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
+ dependencies:
+ function-bind: 1.1.1
+ get-intrinsic: 1.1.1
+ dev: true
+
+ /chalk/2.4.2:
+ resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
+ engines: {node: '>=4'}
+ dependencies:
+ ansi-styles: 3.2.1
+ escape-string-regexp: 1.0.5
+ supports-color: 5.5.0
+ dev: true
+
/chokidar/3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
@@ -163,10 +190,31 @@ packages:
fsevents: 2.3.2
dev: true
+ /color-convert/1.9.3:
+ resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+ dependencies:
+ color-name: 1.1.3
+ dev: true
+
+ /color-name/1.1.3:
+ resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
+ dev: true
+
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
dev: true
+ /cross-spawn/6.0.5:
+ resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
+ engines: {node: '>=4.8'}
+ dependencies:
+ nice-try: 1.0.5
+ path-key: 2.0.1
+ semver: 5.7.1
+ shebang-command: 1.2.0
+ which: 1.3.1
+ dev: true
+
/debug/4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
@@ -184,6 +232,14 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /define-properties/1.1.4:
+ resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-property-descriptors: 1.0.0
+ object-keys: 1.1.1
+ dev: true
+
/detect-indent/6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'}
@@ -193,6 +249,50 @@ packages:
resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
dev: true
+ /error-ex/1.3.2:
+ resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
+ dependencies:
+ is-arrayish: 0.2.1
+ dev: true
+
+ /es-abstract/1.20.1:
+ resolution: {integrity: sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ es-to-primitive: 1.2.1
+ function-bind: 1.1.1
+ function.prototype.name: 1.1.5
+ get-intrinsic: 1.1.1
+ get-symbol-description: 1.0.0
+ has: 1.0.3
+ has-property-descriptors: 1.0.0
+ has-symbols: 1.0.3
+ internal-slot: 1.0.3
+ is-callable: 1.2.4
+ is-negative-zero: 2.0.2
+ is-regex: 1.1.4
+ is-shared-array-buffer: 1.0.2
+ is-string: 1.0.7
+ is-weakref: 1.0.2
+ object-inspect: 1.12.2
+ object-keys: 1.1.1
+ object.assign: 4.1.2
+ regexp.prototype.flags: 1.4.3
+ string.prototype.trimend: 1.0.5
+ string.prototype.trimstart: 1.0.5
+ unbox-primitive: 1.0.2
+ dev: true
+
+ /es-to-primitive/1.2.1:
+ resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ is-callable: 1.2.4
+ is-date-object: 1.0.5
+ is-symbol: 1.0.4
+ dev: true
+
/es6-promise/3.3.1:
resolution: {integrity: sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=}
dev: true
@@ -405,6 +505,11 @@ packages:
esbuild-windows-arm64: 0.14.38
dev: true
+ /escape-string-regexp/1.0.5:
+ resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+ engines: {node: '>=0.8.0'}
+ dev: true
+
/estree-walker/2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true
@@ -436,10 +541,40 @@ packages:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
+ /function.prototype.name/1.1.5:
+ resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ es-abstract: 1.20.1
+ functions-have-names: 1.2.3
+ dev: true
+
+ /functions-have-names/1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+ dev: true
+
/fuzzysort/1.9.0:
resolution: {integrity: sha512-MOxCT0qLTwLqmEwc7UtU045RKef7mc8Qz8eR4r2bLNEq9dy/c3ZKMEFp6IEst69otkQdFZ4FfgH2dmZD+ddX1g==}
dev: false
+ /get-intrinsic/1.1.1:
+ resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==}
+ dependencies:
+ function-bind: 1.1.1
+ has: 1.0.3
+ has-symbols: 1.0.3
+ dev: true
+
+ /get-symbol-description/1.0.0:
+ resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ get-intrinsic: 1.1.1
+ dev: true
+
/glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -462,6 +597,33 @@ packages:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
dev: true
+ /has-bigints/1.0.2:
+ resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
+ dev: true
+
+ /has-flag/3.0.0:
+ resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /has-property-descriptors/1.0.0:
+ resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
+ dependencies:
+ get-intrinsic: 1.1.1
+ dev: true
+
+ /has-symbols/1.0.3:
+ resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /has-tostringtag/1.0.0:
+ resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-symbols: 1.0.3
+ dev: true
+
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
@@ -469,6 +631,10 @@ packages:
function-bind: 1.1.1
dev: true
+ /hosted-git-info/2.8.9:
+ resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
+ dev: true
+
/immutable/4.0.0:
resolution: {integrity: sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==}
dev: true
@@ -484,6 +650,25 @@ packages:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
+ /internal-slot/1.0.3:
+ resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ get-intrinsic: 1.1.1
+ has: 1.0.3
+ side-channel: 1.0.4
+ dev: true
+
+ /is-arrayish/0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+ dev: true
+
+ /is-bigint/1.0.4:
+ resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
+ dependencies:
+ has-bigints: 1.0.2
+ dev: true
+
/is-binary-path/2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@@ -491,12 +676,32 @@ packages:
binary-extensions: 2.2.0
dev: true
+ /is-boolean-object/1.1.2:
+ resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ has-tostringtag: 1.0.0
+ dev: true
+
+ /is-callable/1.2.4:
+ resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
/is-core-module/2.9.0:
resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==}
dependencies:
has: 1.0.3
dev: true
+ /is-date-object/1.0.5:
+ resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.0
+ dev: true
+
/is-extglob/2.1.1:
resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=}
engines: {node: '>=0.10.0'}
@@ -509,20 +714,84 @@ packages:
is-extglob: 2.1.1
dev: true
+ /is-negative-zero/2.0.2:
+ resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /is-number-object/1.0.7:
+ resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.0
+ dev: true
+
/is-number/7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
dev: true
+ /is-regex/1.1.4:
+ resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ has-tostringtag: 1.0.0
+ dev: true
+
+ /is-shared-array-buffer/1.0.2:
+ resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
+ dependencies:
+ call-bind: 1.0.2
+ dev: true
+
+ /is-string/1.0.7:
+ resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.0
+ dev: true
+
+ /is-symbol/1.0.4:
+ resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-symbols: 1.0.3
+ dev: true
+
+ /is-weakref/1.0.2:
+ resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
+ dependencies:
+ call-bind: 1.0.2
+ dev: true
+
+ /isexe/2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ dev: true
+
/jsbi/4.3.0:
resolution: {integrity: sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==}
dev: false
+ /json-parse-better-errors/1.0.2:
+ resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==}
+ dev: true
+
/kleur/4.1.4:
resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==}
engines: {node: '>=6'}
dev: true
+ /load-json-file/4.0.0:
+ resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
+ engines: {node: '>=4'}
+ dependencies:
+ graceful-fs: 4.2.10
+ parse-json: 4.0.0
+ pify: 3.0.0
+ strip-bom: 3.0.0
+ dev: true
+
/magic-string/0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
dependencies:
@@ -536,6 +805,11 @@ packages:
sourcemap-codec: 1.4.8
dev: true
+ /memorystream/0.3.1:
+ resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
+ engines: {node: '>= 0.10.0'}
+ dev: true
+
/microtime/3.0.0:
resolution: {integrity: sha512-SirJr7ZL4ow2iWcb54bekS4aWyBQNVcEDBiwAz9D/sTgY59A+uE8UJU15cp5wyZmPBwg/3zf8lyCJ5NUe1nVlQ==}
engines: {node: '>= 4.0.0'}
@@ -577,6 +851,10 @@ packages:
hasBin: true
dev: true
+ /nice-try/1.0.5:
+ resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
+ dev: true
+
/node-addon-api/1.7.2:
resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==}
dev: true
@@ -586,11 +864,55 @@ packages:
hasBin: true
dev: true
+ /normalize-package-data/2.5.0:
+ resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
+ dependencies:
+ hosted-git-info: 2.8.9
+ resolve: 1.22.0
+ semver: 5.7.1
+ validate-npm-package-license: 3.0.4
+ dev: true
+
/normalize-path/3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
dev: true
+ /npm-run-all/4.1.5:
+ resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==}
+ engines: {node: '>= 4'}
+ hasBin: true
+ dependencies:
+ ansi-styles: 3.2.1
+ chalk: 2.4.2
+ cross-spawn: 6.0.5
+ memorystream: 0.3.1
+ minimatch: 3.1.2
+ pidtree: 0.3.1
+ read-pkg: 3.0.0
+ shell-quote: 1.7.3
+ string.prototype.padend: 3.1.3
+ dev: true
+
+ /object-inspect/1.12.2:
+ resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
+ dev: true
+
+ /object-keys/1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /object.assign/4.1.2:
+ resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ has-symbols: 1.0.3
+ object-keys: 1.1.1
+ dev: true
+
/oblivious-set/1.1.1:
resolution: {integrity: sha512-Oh+8fK09mgGmAshFdH6hSVco6KZmd1tTwNFWj35OvzdmJTMZtAkbn05zar2iG3v6sDs1JLEtOiBGNb6BHwkb2w==}
dev: true
@@ -621,15 +943,35 @@ packages:
p-finally: 1.0.0
dev: true
+ /parse-json/4.0.0:
+ resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==}
+ engines: {node: '>=4'}
+ dependencies:
+ error-ex: 1.3.2
+ json-parse-better-errors: 1.0.2
+ dev: true
+
/path-is-absolute/1.0.1:
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
engines: {node: '>=0.10.0'}
dev: true
+ /path-key/2.0.1:
+ resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
+ engines: {node: '>=4'}
+ dev: true
+
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
+ /path-type/3.0.0:
+ resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==}
+ engines: {node: '>=4'}
+ dependencies:
+ pify: 3.0.0
+ dev: true
+
/picocolors/1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
@@ -639,6 +981,17 @@ packages:
engines: {node: '>=8.6'}
dev: true
+ /pidtree/0.3.1:
+ resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+ dev: true
+
+ /pify/3.0.0:
+ resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==}
+ engines: {node: '>=4'}
+ dev: true
+
/postcss/8.4.13:
resolution: {integrity: sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==}
engines: {node: ^10 || ^12 || >=14}
@@ -648,6 +1001,15 @@ packages:
source-map-js: 1.0.2
dev: true
+ /read-pkg/3.0.0:
+ resolution: {integrity: sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=}
+ engines: {node: '>=4'}
+ dependencies:
+ load-json-file: 4.0.0
+ normalize-package-data: 2.5.0
+ path-type: 3.0.0
+ dev: true
+
/readdirp/3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@@ -659,6 +1021,15 @@ packages:
resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==}
dev: true
+ /regexp.prototype.flags/1.4.3:
+ resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ functions-have-names: 1.2.3
+ dev: true
+
/regexparam/2.0.0:
resolution: {integrity: sha512-gJKwd2MVPWHAIFLsaYDZfyKzHNS4o7E/v8YmNf44vmeV2e4YfVoDToTOKTvE7ab68cRJ++kLuEXJBaEeJVt5ow==}
engines: {node: '>=8'}
@@ -714,6 +1085,35 @@ packages:
source-map-js: 1.0.2
dev: true
+ /semver/5.7.1:
+ resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
+ hasBin: true
+ dev: true
+
+ /shebang-command/1.2.0:
+ resolution: {integrity: sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ shebang-regex: 1.0.0
+ dev: true
+
+ /shebang-regex/1.0.0:
+ resolution: {integrity: sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /shell-quote/1.7.3:
+ resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==}
+ dev: true
+
+ /side-channel/1.0.4:
+ resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
+ dependencies:
+ call-bind: 1.0.2
+ get-intrinsic: 1.1.1
+ object-inspect: 1.12.2
+ dev: true
+
/sorcery/0.10.0:
resolution: {integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=}
hasBin: true
@@ -733,6 +1133,58 @@ packages:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
dev: true
+ /spdx-correct/3.1.1:
+ resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}
+ dependencies:
+ spdx-expression-parse: 3.0.1
+ spdx-license-ids: 3.0.11
+ dev: true
+
+ /spdx-exceptions/2.3.0:
+ resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==}
+ dev: true
+
+ /spdx-expression-parse/3.0.1:
+ resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
+ dependencies:
+ spdx-exceptions: 2.3.0
+ spdx-license-ids: 3.0.11
+ dev: true
+
+ /spdx-license-ids/3.0.11:
+ resolution: {integrity: sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==}
+ dev: true
+
+ /string.prototype.padend/3.1.3:
+ resolution: {integrity: sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ es-abstract: 1.20.1
+ dev: true
+
+ /string.prototype.trimend/1.0.5:
+ resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ es-abstract: 1.20.1
+ dev: true
+
+ /string.prototype.trimstart/1.0.5:
+ resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ es-abstract: 1.20.1
+ dev: true
+
+ /strip-bom/3.0.0:
+ resolution: {integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=}
+ engines: {node: '>=4'}
+ dev: true
+
/strip-indent/3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'}
@@ -740,6 +1192,13 @@ packages:
min-indent: 1.0.1
dev: true
+ /supports-color/5.5.0:
+ resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
+ engines: {node: '>=4'}
+ dependencies:
+ has-flag: 3.0.0
+ dev: true
+
/supports-preserve-symlinks-flag/1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
@@ -828,10 +1287,27 @@ packages:
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
dev: false
+ /typesafe-i18n/5.5.2_typescript@4.6.4:
+ resolution: {integrity: sha512-EcqEHyiSujfIIxqTHuWvhd36Dmbp0rngYp+Z0IilCzRoZXSWGm/CdS5/jhkFySNGUEqtfgdD0gw1EnHbp6/54g==}
+ hasBin: true
+ peerDependencies:
+ typescript: '>=3.5.1'
+ dependencies:
+ typescript: 4.6.4
+ dev: false
+
/typescript/4.6.4:
resolution: {integrity: sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==}
engines: {node: '>=4.2.0'}
hasBin: true
+
+ /unbox-primitive/1.0.2:
+ resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
+ dependencies:
+ call-bind: 1.0.2
+ has-bigints: 1.0.2
+ has-symbols: 1.0.3
+ which-boxed-primitive: 1.0.2
dev: true
/unload/2.3.1:
@@ -841,6 +1317,13 @@ packages:
detect-node: 2.1.0
dev: true
+ /validate-npm-package-license/3.0.4:
+ resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
+ dependencies:
+ spdx-correct: 3.1.1
+ spdx-expression-parse: 3.0.1
+ dev: true
+
/vite/2.9.8_sass@1.51.0:
resolution: {integrity: sha512-zsBGwn5UT3YS0NLSJ7hnR54+vUKfgzMUh/Z9CxF1YKEBVIe213+63jrFLmZphgGI5zXwQCSmqIdbPuE8NJywPw==}
engines: {node: '>=12.2.0'}
@@ -866,6 +1349,23 @@ packages:
fsevents: 2.3.2
dev: true
+ /which-boxed-primitive/1.0.2:
+ resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
+ dependencies:
+ is-bigint: 1.0.4
+ is-boolean-object: 1.1.2
+ is-number-object: 1.0.7
+ is-string: 1.0.7
+ is-symbol: 1.0.4
+ dev: true
+
+ /which/1.3.1:
+ resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
+ hasBin: true
+ dependencies:
+ isexe: 2.0.0
+ dev: true
+
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: true
diff --git a/apps/web-shared/package.json b/apps/web-shared/package.json
index e30d0ba..fb14b4b 100644
--- a/apps/web-shared/package.json
+++ b/apps/web-shared/package.json
@@ -3,10 +3,11 @@
"version": "0.0.1",
"private": "true",
"devDependencies": {
+ "@js-temporal/polyfill": "^0.4.1",
+ "fuzzysort": "^1.9.0",
"svelte": "^3.48.0",
+ "svelte-feather-icons": "^4.0.0",
"svelte-spa-router": "^3.2.0",
- "typescript": "4.6.4",
- "@js-temporal/polyfill": "^0.4.1",
- "fuzzysort": "^1.9.0"
+ "typescript": "4.6.4"
}
}
diff --git a/apps/web-shared/pnpm-lock.yaml b/apps/web-shared/pnpm-lock.yaml
index 3b56115..88ebee5 100644
--- a/apps/web-shared/pnpm-lock.yaml
+++ b/apps/web-shared/pnpm-lock.yaml
@@ -2,27 +2,19 @@ lockfileVersion: 5.4
specifiers:
'@js-temporal/polyfill': ^0.4.1
- '@sveltejs/vite-plugin-svelte': 1.0.0-next.43
fuzzysort: ^1.9.0
- sass: ^1.51.0
svelte: ^3.48.0
- svelte-preprocess: ^4.10.6
+ svelte-feather-icons: ^4.0.0
svelte-spa-router: ^3.2.0
typescript: 4.6.4
- vite: ^2.9.8
-dependencies:
+devDependencies:
'@js-temporal/polyfill': 0.4.1
fuzzysort: 1.9.0
-
-devDependencies:
- '@sveltejs/vite-plugin-svelte': 1.0.0-next.43_svelte@3.48.0+vite@2.9.8
- sass: 1.51.0
svelte: 3.48.0
- svelte-preprocess: 4.10.6_24ezlekk4ocevlsjgs2qnqmjum
+ svelte-feather-icons: 4.0.0
svelte-spa-router: 3.2.0
typescript: 4.6.4
- vite: 2.9.8_sass@1.51.0
packages:
@@ -32,543 +24,14 @@ packages:
dependencies:
jsbi: 4.3.0
tslib: 2.4.0
- dev: false
-
- /@rollup/pluginutils/4.2.1:
- resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
- engines: {node: '>= 8.0.0'}
- dependencies:
- estree-walker: 2.0.2
- picomatch: 2.3.1
- dev: true
-
- /@sveltejs/vite-plugin-svelte/1.0.0-next.43_svelte@3.48.0+vite@2.9.8:
- resolution: {integrity: sha512-MzeczqGrnDmbAldw/LfXV/dhpLC2bdUzuMhcx0C2j79V2uNzQERHDinxXnG2AVTCTjSpbQxzQwMMmYflnI7W1g==}
- engines: {node: ^14.13.1 || >= 16}
- peerDependencies:
- diff-match-patch: ^1.0.5
- svelte: ^3.44.0
- vite: ^2.9.0
- peerDependenciesMeta:
- diff-match-patch:
- optional: true
- dependencies:
- '@rollup/pluginutils': 4.2.1
- debug: 4.3.4
- deepmerge: 4.2.2
- kleur: 4.1.4
- magic-string: 0.26.1
- svelte: 3.48.0
- svelte-hmr: 0.14.11_svelte@3.48.0
- vite: 2.9.8_sass@1.51.0
- transitivePeerDependencies:
- - supports-color
- dev: true
-
- /@types/node/17.0.31:
- resolution: {integrity: sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==}
- dev: true
-
- /@types/pug/2.0.6:
- resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
- dev: true
-
- /@types/sass/1.43.1:
- resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
- dependencies:
- '@types/node': 17.0.31
- dev: true
-
- /anymatch/3.1.2:
- resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
- engines: {node: '>= 8'}
- dependencies:
- normalize-path: 3.0.0
- picomatch: 2.3.1
- dev: true
-
- /balanced-match/1.0.2:
- resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
- dev: true
-
- /binary-extensions/2.2.0:
- resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
- engines: {node: '>=8'}
- dev: true
-
- /brace-expansion/1.1.11:
- resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
- dependencies:
- balanced-match: 1.0.2
- concat-map: 0.0.1
- dev: true
-
- /braces/3.0.2:
- resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
- engines: {node: '>=8'}
- dependencies:
- fill-range: 7.0.1
- dev: true
-
- /buffer-crc32/0.2.13:
- resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=}
- dev: true
-
- /chokidar/3.5.3:
- resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
- engines: {node: '>= 8.10.0'}
- dependencies:
- anymatch: 3.1.2
- braces: 3.0.2
- glob-parent: 5.1.2
- is-binary-path: 2.1.0
- is-glob: 4.0.3
- normalize-path: 3.0.0
- readdirp: 3.6.0
- optionalDependencies:
- fsevents: 2.3.2
- dev: true
-
- /concat-map/0.0.1:
- resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
- dev: true
-
- /debug/4.3.4:
- resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
- engines: {node: '>=6.0'}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
- dependencies:
- ms: 2.1.2
- dev: true
-
- /deepmerge/4.2.2:
- resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
- engines: {node: '>=0.10.0'}
- dev: true
-
- /detect-indent/6.1.0:
- resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
- engines: {node: '>=8'}
- dev: true
-
- /es6-promise/3.3.1:
- resolution: {integrity: sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=}
- dev: true
-
- /esbuild-android-64/0.14.38:
- resolution: {integrity: sha512-aRFxR3scRKkbmNuGAK+Gee3+yFxkTJO/cx83Dkyzo4CnQl/2zVSurtG6+G86EQIZ+w+VYngVyK7P3HyTBKu3nw==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-android-arm64/0.14.38:
- resolution: {integrity: sha512-L2NgQRWuHFI89IIZIlpAcINy9FvBk6xFVZ7xGdOwIm8VyhX1vNCEqUJO3DPSSy945Gzdg98cxtNt8Grv1CsyhA==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-darwin-64/0.14.38:
- resolution: {integrity: sha512-5JJvgXkX87Pd1Og0u/NJuO7TSqAikAcQQ74gyJ87bqWRVeouky84ICoV4sN6VV53aTW+NE87qLdGY4QA2S7KNA==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-darwin-arm64/0.14.38:
- resolution: {integrity: sha512-eqF+OejMI3mC5Dlo9Kdq/Ilbki9sQBw3QlHW3wjLmsLh+quNfHmGMp3Ly1eWm981iGBMdbtSS9+LRvR2T8B3eQ==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-freebsd-64/0.14.38:
- resolution: {integrity: sha512-epnPbhZUt93xV5cgeY36ZxPXDsQeO55DppzsIgWM8vgiG/Rz+qYDLmh5ts3e+Ln1wA9dQ+nZmVHw+RjaW3I5Ig==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-freebsd-arm64/0.14.38:
- resolution: {integrity: sha512-/9icXUYJWherhk+y5fjPI5yNUdFPtXHQlwP7/K/zg8t8lQdHVj20SqU9/udQmeUo5pDFHMYzcEFfJqgOVeKNNQ==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-32/0.14.38:
- resolution: {integrity: sha512-QfgfeNHRFvr2XeHFzP8kOZVnal3QvST3A0cgq32ZrHjSMFTdgXhMhmWdKzRXP/PKcfv3e2OW9tT9PpcjNvaq6g==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-64/0.14.38:
- resolution: {integrity: sha512-uuZHNmqcs+Bj1qiW9k/HZU3FtIHmYiuxZ/6Aa+/KHb/pFKr7R3aVqvxlAudYI9Fw3St0VCPfv7QBpUITSmBR1Q==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-arm/0.14.38:
- resolution: {integrity: sha512-FiFvQe8J3VKTDXG01JbvoVRXQ0x6UZwyrU4IaLBZeq39Bsbatd94Fuc3F1RGqPF5RbIWW7RvkVQjn79ejzysnA==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-arm64/0.14.38:
- resolution: {integrity: sha512-HlMGZTEsBrXrivr64eZ/EO0NQM8H8DuSENRok9d+Jtvq8hOLzrxfsAT9U94K3KOGk2XgCmkaI2KD8hX7F97lvA==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-mips64le/0.14.38:
- resolution: {integrity: sha512-qd1dLf2v7QBiI5wwfil9j0HG/5YMFBAmMVmdeokbNAMbcg49p25t6IlJFXAeLzogv1AvgaXRXvgFNhScYEUXGQ==}
- engines: {node: '>=12'}
- cpu: [mips64el]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-ppc64le/0.14.38:
- resolution: {integrity: sha512-mnbEm7o69gTl60jSuK+nn+pRsRHGtDPfzhrqEUXyCl7CTOCLtWN2bhK8bgsdp6J/2NyS/wHBjs1x8aBWwP2X9Q==}
- engines: {node: '>=12'}
- cpu: [ppc64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-riscv64/0.14.38:
- resolution: {integrity: sha512-+p6YKYbuV72uikChRk14FSyNJZ4WfYkffj6Af0/Tw63/6TJX6TnIKE+6D3xtEc7DeDth1fjUOEqm+ApKFXbbVQ==}
- engines: {node: '>=12'}
- cpu: [riscv64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-s390x/0.14.38:
- resolution: {integrity: sha512-0zUsiDkGJiMHxBQ7JDU8jbaanUY975CdOW1YDrurjrM0vWHfjv9tLQsW9GSyEb/heSK1L5gaweRjzfUVBFoybQ==}
- engines: {node: '>=12'}
- cpu: [s390x]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-netbsd-64/0.14.38:
- resolution: {integrity: sha512-cljBAApVwkpnJZfnRVThpRBGzCi+a+V9Ofb1fVkKhtrPLDYlHLrSYGtmnoTVWDQdU516qYI8+wOgcGZ4XIZh0Q==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [netbsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-openbsd-64/0.14.38:
- resolution: {integrity: sha512-CDswYr2PWPGEPpLDUO50mL3WO/07EMjnZDNKpmaxUPsrW+kVM3LoAqr/CE8UbzugpEiflYqJsGPLirThRB18IQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [openbsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-sunos-64/0.14.38:
- resolution: {integrity: sha512-2mfIoYW58gKcC3bck0j7lD3RZkqYA7MmujFYmSn9l6TiIcAMpuEvqksO+ntBgbLep/eyjpgdplF7b+4T9VJGOA==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [sunos]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-windows-32/0.14.38:
- resolution: {integrity: sha512-L2BmEeFZATAvU+FJzJiRLFUP+d9RHN+QXpgaOrs2klshoAm1AE6Us4X6fS9k33Uy5SzScn2TpcgecbqJza1Hjw==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-windows-64/0.14.38:
- resolution: {integrity: sha512-Khy4wVmebnzue8aeSXLC+6clo/hRYeNIm0DyikoEqX+3w3rcvrhzpoix0S+MF9vzh6JFskkIGD7Zx47ODJNyCw==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-windows-arm64/0.14.38:
- resolution: {integrity: sha512-k3FGCNmHBkqdJXuJszdWciAH77PukEyDsdIryEHn9cKLQFxzhT39dSumeTuggaQcXY57UlmLGIkklWZo2qzHpw==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild/0.14.38:
- resolution: {integrity: sha512-12fzJ0fsm7gVZX1YQ1InkOE5f9Tl7cgf6JPYXRJtPIoE0zkWAbHdPHVPPaLi9tYAcEBqheGzqLn/3RdTOyBfcA==}
- engines: {node: '>=12'}
- hasBin: true
- requiresBuild: true
- optionalDependencies:
- esbuild-android-64: 0.14.38
- esbuild-android-arm64: 0.14.38
- esbuild-darwin-64: 0.14.38
- esbuild-darwin-arm64: 0.14.38
- esbuild-freebsd-64: 0.14.38
- esbuild-freebsd-arm64: 0.14.38
- esbuild-linux-32: 0.14.38
- esbuild-linux-64: 0.14.38
- esbuild-linux-arm: 0.14.38
- esbuild-linux-arm64: 0.14.38
- esbuild-linux-mips64le: 0.14.38
- esbuild-linux-ppc64le: 0.14.38
- esbuild-linux-riscv64: 0.14.38
- esbuild-linux-s390x: 0.14.38
- esbuild-netbsd-64: 0.14.38
- esbuild-openbsd-64: 0.14.38
- esbuild-sunos-64: 0.14.38
- esbuild-windows-32: 0.14.38
- esbuild-windows-64: 0.14.38
- esbuild-windows-arm64: 0.14.38
- dev: true
-
- /estree-walker/2.0.2:
- resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
- dev: true
-
- /fill-range/7.0.1:
- resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
- engines: {node: '>=8'}
- dependencies:
- to-regex-range: 5.0.1
- dev: true
-
- /fs.realpath/1.0.0:
- resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
- dev: true
-
- /fsevents/2.3.2:
- resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
- engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /function-bind/1.1.1:
- resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
/fuzzysort/1.9.0:
resolution: {integrity: sha512-MOxCT0qLTwLqmEwc7UtU045RKef7mc8Qz8eR4r2bLNEq9dy/c3ZKMEFp6IEst69otkQdFZ4FfgH2dmZD+ddX1g==}
- dev: false
-
- /glob-parent/5.1.2:
- resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
- engines: {node: '>= 6'}
- dependencies:
- is-glob: 4.0.3
- dev: true
-
- /glob/7.2.0:
- resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
- dependencies:
- fs.realpath: 1.0.0
- inflight: 1.0.6
- inherits: 2.0.4
- minimatch: 3.1.2
- once: 1.4.0
- path-is-absolute: 1.0.1
- dev: true
-
- /graceful-fs/4.2.10:
- resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
- dev: true
-
- /has/1.0.3:
- resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
- engines: {node: '>= 0.4.0'}
- dependencies:
- function-bind: 1.1.1
- dev: true
-
- /immutable/4.0.0:
- resolution: {integrity: sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==}
- dev: true
-
- /inflight/1.0.6:
- resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
- dependencies:
- once: 1.4.0
- wrappy: 1.0.2
- dev: true
-
- /inherits/2.0.4:
- resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
- dev: true
-
- /is-binary-path/2.1.0:
- resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
- engines: {node: '>=8'}
- dependencies:
- binary-extensions: 2.2.0
- dev: true
-
- /is-core-module/2.9.0:
- resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==}
- dependencies:
- has: 1.0.3
- dev: true
-
- /is-extglob/2.1.1:
- resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=}
- engines: {node: '>=0.10.0'}
- dev: true
-
- /is-glob/4.0.3:
- resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
- engines: {node: '>=0.10.0'}
- dependencies:
- is-extglob: 2.1.1
- dev: true
-
- /is-number/7.0.0:
- resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
- engines: {node: '>=0.12.0'}
dev: true
/jsbi/4.3.0:
resolution: {integrity: sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==}
- dev: false
-
- /kleur/4.1.4:
- resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==}
- engines: {node: '>=6'}
- dev: true
-
- /magic-string/0.25.9:
- resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
- dependencies:
- sourcemap-codec: 1.4.8
- dev: true
-
- /magic-string/0.26.1:
- resolution: {integrity: sha512-ndThHmvgtieXe8J/VGPjG+Apu7v7ItcD5mhEIvOscWjPF/ccOiLxHaSuCAS2G+3x4GKsAbT8u7zdyamupui8Tg==}
- engines: {node: '>=12'}
- dependencies:
- sourcemap-codec: 1.4.8
- dev: true
-
- /min-indent/1.0.1:
- resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
- engines: {node: '>=4'}
- dev: true
-
- /minimatch/3.1.2:
- resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
- dependencies:
- brace-expansion: 1.1.11
- dev: true
-
- /minimist/1.2.6:
- resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
- dev: true
-
- /mkdirp/0.5.6:
- resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
- hasBin: true
- dependencies:
- minimist: 1.2.6
- dev: true
-
- /ms/2.1.2:
- resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
- dev: true
-
- /nanoid/3.3.4:
- resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
- engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
- hasBin: true
- dev: true
-
- /normalize-path/3.0.0:
- resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
- engines: {node: '>=0.10.0'}
- dev: true
-
- /once/1.4.0:
- resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
- dependencies:
- wrappy: 1.0.2
- dev: true
-
- /path-is-absolute/1.0.1:
- resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
- engines: {node: '>=0.10.0'}
- dev: true
-
- /path-parse/1.0.7:
- resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
- dev: true
-
- /picocolors/1.0.0:
- resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
- dev: true
-
- /picomatch/2.3.1:
- resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
- engines: {node: '>=8.6'}
- dev: true
-
- /postcss/8.4.13:
- resolution: {integrity: sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==}
- engines: {node: ^10 || ^12 || >=14}
- dependencies:
- nanoid: 3.3.4
- picocolors: 1.0.0
- source-map-js: 1.0.2
- dev: true
-
- /readdirp/3.6.0:
- resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
- engines: {node: '>=8.10.0'}
- dependencies:
- picomatch: 2.3.1
dev: true
/regexparam/2.0.0:
@@ -576,139 +39,10 @@ packages:
engines: {node: '>=8'}
dev: true
- /resolve/1.22.0:
- resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==}
- hasBin: true
+ /svelte-feather-icons/4.0.0:
+ resolution: {integrity: sha512-4ieUsjp+VYa1r6y80jDt9zRiRUZyJNbESpRdHdJJhiBubyuXX96A7f1UZSK4olxzP6Qsg5ZAuyZlnmvD+/swAA==}
dependencies:
- is-core-module: 2.9.0
- path-parse: 1.0.7
- supports-preserve-symlinks-flag: 1.0.0
- dev: true
-
- /rimraf/2.7.1:
- resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
- hasBin: true
- dependencies:
- glob: 7.2.0
- dev: true
-
- /rollup/2.72.1:
- resolution: {integrity: sha512-NTc5UGy/NWFGpSqF1lFY8z9Adri6uhyMLI6LvPAXdBKoPRFhIIiBUpt+Qg2awixqO3xvzSijjhnb4+QEZwJmxA==}
- engines: {node: '>=10.0.0'}
- hasBin: true
- optionalDependencies:
- fsevents: 2.3.2
- dev: true
-
- /sander/0.5.1:
- resolution: {integrity: sha1-dB4kXiMfB8r7b98PEzrfohalAq0=}
- dependencies:
- es6-promise: 3.3.1
- graceful-fs: 4.2.10
- mkdirp: 0.5.6
- rimraf: 2.7.1
- dev: true
-
- /sass/1.51.0:
- resolution: {integrity: sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==}
- engines: {node: '>=12.0.0'}
- hasBin: true
- dependencies:
- chokidar: 3.5.3
- immutable: 4.0.0
- source-map-js: 1.0.2
- dev: true
-
- /sorcery/0.10.0:
- resolution: {integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=}
- hasBin: true
- dependencies:
- buffer-crc32: 0.2.13
- minimist: 1.2.6
- sander: 0.5.1
- sourcemap-codec: 1.4.8
- dev: true
-
- /source-map-js/1.0.2:
- resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
- engines: {node: '>=0.10.0'}
- dev: true
-
- /sourcemap-codec/1.4.8:
- resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
- dev: true
-
- /strip-indent/3.0.0:
- resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
- engines: {node: '>=8'}
- dependencies:
- min-indent: 1.0.1
- dev: true
-
- /supports-preserve-symlinks-flag/1.0.0:
- resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
- engines: {node: '>= 0.4'}
- dev: true
-
- /svelte-hmr/0.14.11_svelte@3.48.0:
- resolution: {integrity: sha512-R9CVfX6DXxW1Kn45Jtmx+yUe+sPhrbYSUp7TkzbW0jI5fVPn6lsNG9NEs5dFg5qRhFNAoVdRw5qQDLALNKhwbQ==}
- engines: {node: ^12.20 || ^14.13.1 || >= 16}
- peerDependencies:
- svelte: '>=3.19.0'
- dependencies:
- svelte: 3.48.0
- dev: true
-
- /svelte-preprocess/4.10.6_24ezlekk4ocevlsjgs2qnqmjum:
- resolution: {integrity: sha512-I2SV1w/AveMvgIQlUF/ZOO3PYVnhxfcpNyGt8pxpUVhPfyfL/CZBkkw/KPfuFix5FJ9TnnNYMhACK3DtSaYVVQ==}
- engines: {node: '>= 9.11.2'}
- requiresBuild: true
- peerDependencies:
- '@babel/core': ^7.10.2
- coffeescript: ^2.5.1
- less: ^3.11.3 || ^4.0.0
- node-sass: '*'
- postcss: ^7 || ^8
- postcss-load-config: ^2.1.0 || ^3.0.0
- pug: ^3.0.0
- sass: ^1.26.8
- stylus: ^0.55.0
- sugarss: ^2.0.0
- svelte: ^3.23.0
- typescript: ^3.9.5 || ^4.0.0
- peerDependenciesMeta:
- '@babel/core':
- optional: true
- coffeescript:
- optional: true
- less:
- optional: true
- node-sass:
- optional: true
- postcss:
- optional: true
- postcss-load-config:
- optional: true
- pug:
- optional: true
- sass:
- optional: true
- stylus:
- optional: true
- sugarss:
- optional: true
- typescript:
- optional: true
- dependencies:
- '@types/pug': 2.0.6
- '@types/sass': 1.43.1
- detect-indent: 6.1.0
- magic-string: 0.25.9
- sass: 1.51.0
- sorcery: 0.10.0
- strip-indent: 3.0.0
svelte: 3.48.0
- typescript: 4.6.4
dev: true
/svelte-spa-router/3.2.0:
@@ -722,48 +56,12 @@ packages:
engines: {node: '>= 8'}
dev: true
- /to-regex-range/5.0.1:
- resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
- engines: {node: '>=8.0'}
- dependencies:
- is-number: 7.0.0
- dev: true
-
/tslib/2.4.0:
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
- dev: false
+ dev: true
/typescript/4.6.4:
resolution: {integrity: sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
-
- /vite/2.9.8_sass@1.51.0:
- resolution: {integrity: sha512-zsBGwn5UT3YS0NLSJ7hnR54+vUKfgzMUh/Z9CxF1YKEBVIe213+63jrFLmZphgGI5zXwQCSmqIdbPuE8NJywPw==}
- engines: {node: '>=12.2.0'}
- hasBin: true
- peerDependencies:
- less: '*'
- sass: '*'
- stylus: '*'
- peerDependenciesMeta:
- less:
- optional: true
- sass:
- optional: true
- stylus:
- optional: true
- dependencies:
- esbuild: 0.14.38
- postcss: 8.4.13
- resolve: 1.22.0
- rollup: 2.72.1
- sass: 1.51.0
- optionalDependencies:
- fsevents: 2.3.2
- dev: true
-
- /wrappy/1.0.2:
- resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
- dev: true
diff --git a/apps/web-shared/src/components/blowout-toolbelt.svelte b/apps/web-shared/src/components/blowout-toolbelt.svelte
new file mode 100644
index 0000000..69e9902
--- /dev/null
+++ b/apps/web-shared/src/components/blowout-toolbelt.svelte
@@ -0,0 +1,60 @@
+<script>
+ import {createEventDispatcher} from "svelte";
+ import ThemeSwitcher from "./theme-switcher.svelte";
+ import ThemeSwitcherIcon from "./theme-switcher-icon.svelte";
+ import LocaleSwitcher from "./locale-switcher.svelte";
+ import LocaleSwitcherIcon from "./locale-switcher-icon.svelte";
+ import {ChevronsRightIcon, ChevronsLeftIcon} from "svelte-feather-icons";
+
+ const dispatch = createEventDispatcher();
+
+ function locale_change(event) {
+ dispatch("change", {name: "locale", value: event.detail})
+ }
+
+ let expanded = false;
+ const localeSwitcher = {
+ show: false,
+ selection: "preffered"
+ };
+
+ const themeSwitcher = {
+ show: false,
+ selection: "system"
+ };
+</script>
+<style>
+ .blowout {
+ right: -80px;
+ }
+
+ .expanded {
+ right: 0 !important;
+ }
+</style>
+<aside class="blowout position-fixed bg-light inner-glow shadow-xs padding-xxs bottom-50% right-0 z-index-2 {expanded ? 'expanded' : ''}">
+ <LocaleSwitcher bind:show="{localeSwitcher.show}"
+ glow="{false}"
+ on:change={locale_change}
+ bind:selection="{localeSwitcher.selection}"/>
+ <ThemeSwitcher bind:show="{themeSwitcher.show}"
+ glow="{false}"
+ bind:selection="{themeSwitcher.selection}"/>
+ <div class="flex flex-row gap-sm justify-end">
+ {#if !expanded}
+ <div class="flex ld-switch-btn"
+ on:click={() => expanded = true}>
+ <ChevronsLeftIcon/>
+ </div>
+ {:else}
+ <div class="flex ld-switch-btn"
+ on:click={() => expanded = false}>
+ <ChevronsRightIcon/>
+ </div>
+ {/if}
+ <LocaleSwitcherIcon bind:show="{localeSwitcher.show}"
+ bind:selection="{localeSwitcher.selection}"/>
+ <ThemeSwitcherIcon bind:show="{themeSwitcher.show}"
+ bind:selection="{themeSwitcher.selection}"/>
+ </div>
+</aside>
diff --git a/apps/web-shared/src/components/link-card.svelte b/apps/web-shared/src/components/link-card.svelte
index 0c15a53..85738c7 100644
--- a/apps/web-shared/src/components/link-card.svelte
+++ b/apps/web-shared/src/components/link-card.svelte
@@ -7,13 +7,13 @@
export let title = null;
</script>
-<a class="link-card flex flex-column bg-light cursor-pointer radius-md {$$restProps.class??''}"
+<a class="link-card flex flex-column bg-light cursor-pointer radius-sm inner-glow {$$restProps.class??''}"
{href}
{target}
{title}
on:click
aria-label="Link label">
- <div class="padding-md">
+ <div class="padding-sm">
<div class="flex flex-wrap gap-xs items-center">
<slot name="icon"></slot>
<div class="line-height-xs">
diff --git a/apps/web-shared/src/components/locale-switcher-icon.svelte b/apps/web-shared/src/components/locale-switcher-icon.svelte
new file mode 100644
index 0000000..d2776a1
--- /dev/null
+++ b/apps/web-shared/src/components/locale-switcher-icon.svelte
@@ -0,0 +1,16 @@
+<script>
+ import {GlobeIcon} from "svelte-feather-icons";
+
+ export let show = false;
+ export let selection = "preffered";
+</script>
+<div data-locale-switcher-element
+ class="ld-switch flex">
+ <button class="reset ld-switch-btn"
+ on:click={() => (show = !show)}>
+ <div class="ld-switch-btn__icon-wrapper ld-switch-btn__icon-wrapper--in"
+ aria-hidden="true">
+ <GlobeIcon/>
+ </div>
+ </button>
+</div>
diff --git a/apps/web-shared/src/components/locale-switcher.svelte b/apps/web-shared/src/components/locale-switcher.svelte
new file mode 100644
index 0000000..2dae026
--- /dev/null
+++ b/apps/web-shared/src/components/locale-switcher.svelte
@@ -0,0 +1,64 @@
+<script>
+ import {base_domain, CookieNames} from "$shared/lib/configuration";
+ import {get_cookie, set_cookie} from "$shared/lib/helpers";
+ import {createEventDispatcher, onMount} from "svelte";
+
+ const dispatch = createEventDispatcher();
+
+ export let glow = false;
+ export let show = false;
+ export let selection = "preffered";
+ export let size;
+
+ function change(to) {
+ selection = to;
+ set_cookie(CookieNames.locale, selection, base_domain());
+ dispatch("change", selection);
+ }
+
+ onMount(() => {
+ selection = get_cookie(CookieNames.locale);
+ document.addEventListener("keydown", (e) => {
+ if (e.code === "Esc") {
+ show = false;
+ }
+ });
+ document.addEventListener("click", (e) => {
+ if (e.target.closest("[data-locale-switcher-element]") === null) {
+ show = false;
+ }
+ });
+ });
+</script>
+
+<div class="bg-light padding-x-xs padding-bottom-xs padding-top-xxxs radius-md {glow ? 'inner-glow shadow-xs':''}"
+ data-locale-switcher-element
+ class:is-hidden={!show}
+ role="listbox">
+ <div class="flex flex-wrap flex-column"
+ role="group">
+ <div class="margin-bottom-xs flex-grow">
+ <span class="text-xs color-contrast-medium">Language</span>
+ </div>
+ <div class="flex gap-xs flex-row">
+ <div class="ld-switch-popover__option"
+ aria-selected="{selection === 'en' ? 'true' : 'false'}"
+ on:click={() => change("en")}
+ role="option">
+ <div class="text-xs margin-top-xxxs padding-x-xxxxs">English</div>
+ </div>
+ <div class="ld-switch-popover__option"
+ aria-selected="{selection === 'nb' ? 'true' : 'false'}"
+ on:click={() => change("nb")}
+ role="option">
+ <div class="text-xs margin-top-xxxs padding-x-xxxxs">Norsk</div>
+ </div>
+ <div class="ld-switch-popover__option"
+ aria-selected="{selection === 'preffered' ? 'true' : 'false'}"
+ on:click={() => change("preffered")}
+ role="option">
+ <div class="text-xs margin-top-xxxs padding-x-xxxxs">Default</div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/apps/web-shared/src/components/theme-switcher-icon.svelte b/apps/web-shared/src/components/theme-switcher-icon.svelte
new file mode 100644
index 0000000..1531ab2
--- /dev/null
+++ b/apps/web-shared/src/components/theme-switcher-icon.svelte
@@ -0,0 +1,248 @@
+<script>
+ export let show = false;
+ export let selection = "";
+</script>
+
+<div class="ld-switch flex"
+ data-theme-switcher-element>
+ <button class="reset ld-switch-btn"
+ on:click={() => show =!show}>
+ <span class="sr-only">{selection}</span>
+ <div class="ld-switch-btn__icon-wrapper ld-switch-btn__icon-wrapper--in"
+ aria-hidden="true">
+ {#if selection === "dark"}
+ <svg class="icon ld-switch-btn__icon"
+ viewBox="0 0 20 20">
+ <title>dark</title>
+ <g fill="currentColor">
+ <path d="M11.964 3.284c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036a7 7 0 1 0 8.68-8.68z"
+ fill-opacity=".2"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"></path>
+ <path d="M7 4a.979.979 0 0 1-1-1 1 1 0 0 0-2 0 .979.979 0 0 1-1 1 1 1 0 0 0 0 2 .979.979 0 0 1 1 1 1 1 0 0 0 2 0 .979.979 0 0 1 1-1 1 1 0 0 0 0-2z"></path>
+ </g>
+ </svg>
+ {:else if selection === "light"}
+ <svg class="icon ld-switch-btn__icon"
+ viewBox="0 0 20 20"><title>light</title>
+ <g fill="currentColor">
+ <circle cx="10"
+ cy="10"
+ r="4"
+ fill-opacity=".2"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"></circle>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M10 1v1.5"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M16.364 3.636l-1.061 1.061"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M19 10h-1.5"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M16.364 16.364l-1.061-1.061"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M10 19v-1.5"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M3.636 16.364l1.061-1.061"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M1 10h1.5"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M3.636 3.636l1.061 1.061"></path>
+ </g>
+ </svg>
+ {:else }
+ <svg class="icon ld-switch-btn__icon"
+ viewBox="0 0 20 20"><title>light-auto</title>
+ <g fill="currentColor">
+ <path d="M10 14a4 4 0 1 1 3.465-6"
+ fill-opacity=".2"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M12 18l2.5-7h1l2.5 7"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M12.714 16h4.572"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M10 1v1.5"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M16.364 3.636l-1.061 1.061"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M3.636 16.364l1.061-1.061"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M1 10h1.5"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M3.636 3.636l1.061 1.061"></path>
+ </g>
+ </svg>
+ {/if}
+ </div>
+
+ <div class="ld-switch-btn__icon-wrapper js-ld-switch-icon"
+ aria-hidden="true">
+ {#if selection === "dark"}
+ <svg class="icon ld-switch-btn__icon"
+ viewBox="0 0 20 20">
+ <title>dark</title>
+ <g fill="currentColor">
+ <path d="M11.964 3.284c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036a7 7 0 1 0 8.68-8.68z"
+ fill-opacity=".2"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"></path>
+ <path d="M7 4a.979.979 0 0 1-1-1 1 1 0 0 0-2 0 .979.979 0 0 1-1 1 1 1 0 0 0 0 2 .979.979 0 0 1 1 1 1 1 0 0 0 2 0 .979.979 0 0 1 1-1 1 1 0 0 0 0-2z"></path>
+ </g>
+ </svg>
+ {:else if selection === "light"}
+ <svg class="icon ld-switch-btn__icon"
+ viewBox="0 0 20 20">
+ <title>light-auto</title>
+ <g fill="currentColor">
+ <path d="M10 14a4 4 0 1 1 3.465-6"
+ fill-opacity=".2"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M12 18l2.5-7h1l2.5 7"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M12.714 16h4.572"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M10 1v1.5"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M16.364 3.636l-1.061 1.061"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M3.636 16.364l1.061-1.061"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M1 10h1.5"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M3.636 3.636l1.061 1.061"></path>
+ </g>
+ </svg>
+ {:else }
+ <svg class="icon ld-switch-btn__icon"
+ viewBox="0 0 20 20">
+ <title>dark-auto</title>
+ <g fill="currentColor">
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M12 18l2.5-7h1l2.5 7"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M12.714 16h4.572"></path>
+ <path d="M12.146 10.159A2.5 2.5 0 0 1 14.5 8.5h1a2.5 2.5 0 0 1 1.412.441 7 7 0 0 0-4.948-5.657c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036a6.99 6.99 0 0 0 6.427 5.012z"
+ fill-opacity=".2"></path>
+ <path d="M16.71 8a7.015 7.015 0 0 0-4.746-4.716c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036A7.006 7.006 0 0 0 9 16.929"
+ fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"></path>
+ <path d="M7 4a.979.979 0 0 1-1-1 1 1 0 0 0-2 0 .979.979 0 0 1-1 1 1 1 0 0 0 0 2 .979.979 0 0 1 1 1 1 1 0 0 0 2 0 .979.979 0 0 1 1-1 1 1 0 0 0 0-2z"></path>
+ </g>
+ </svg>
+ {/if}
+ </div>
+ </button>
+</div>
diff --git a/apps/web-shared/src/components/theme-switcher.svelte b/apps/web-shared/src/components/theme-switcher.svelte
index fd14059..397bad4 100644
--- a/apps/web-shared/src/components/theme-switcher.svelte
+++ b/apps/web-shared/src/components/theme-switcher.svelte
@@ -6,6 +6,7 @@
type theme = "system"|"dark"|"light";
export let show = false;
+ export let glow = false;
export let selection: theme = "system";
export let size;
let prefers = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
@@ -42,253 +43,8 @@
}
</script>
-<div class="ld-switch"
- data-theme-switcher-element>
- <button class="reset ld-switch-btn"
- on:click={() => show =!show}>
- <span class="sr-only">{selection}</span>
- <div class="ld-switch-btn__icon-wrapper ld-switch-btn__icon-wrapper--in"
- aria-hidden="true">
- {#if selection === "dark"}
- <svg class="icon ld-switch-btn__icon"
- viewBox="0 0 20 20">
- <title>dark</title>
- <g fill="currentColor">
- <path d="M11.964 3.284c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036a7 7 0 1 0 8.68-8.68z"
- fill-opacity=".2"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"></path>
- <path d="M7 4a.979.979 0 0 1-1-1 1 1 0 0 0-2 0 .979.979 0 0 1-1 1 1 1 0 0 0 0 2 .979.979 0 0 1 1 1 1 1 0 0 0 2 0 .979.979 0 0 1 1-1 1 1 0 0 0 0-2z"></path>
- </g>
- </svg>
- {:else if selection === "light"}
- <svg class="icon ld-switch-btn__icon"
- viewBox="0 0 20 20"><title>light</title>
- <g fill="currentColor">
- <circle cx="10"
- cy="10"
- r="4"
- fill-opacity=".2"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"></circle>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M10 1v1.5"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M16.364 3.636l-1.061 1.061"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M19 10h-1.5"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M16.364 16.364l-1.061-1.061"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M10 19v-1.5"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M3.636 16.364l1.061-1.061"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M1 10h1.5"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M3.636 3.636l1.061 1.061"></path>
- </g>
- </svg>
- {:else }
- <svg class="icon ld-switch-btn__icon"
- viewBox="0 0 20 20"><title>light-auto</title>
- <g fill="currentColor">
- <path d="M10 14a4 4 0 1 1 3.465-6"
- fill-opacity=".2"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M12 18l2.5-7h1l2.5 7"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M12.714 16h4.572"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M10 1v1.5"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M16.364 3.636l-1.061 1.061"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M3.636 16.364l1.061-1.061"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M1 10h1.5"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M3.636 3.636l1.061 1.061"></path>
- </g>
- </svg>
- {/if}
- </div>
-
- <div class="ld-switch-btn__icon-wrapper js-ld-switch-icon"
- aria-hidden="true">
- {#if selection === "dark"}
- <svg class="icon ld-switch-btn__icon"
- viewBox="0 0 20 20">
- <title>dark</title>
- <g fill="currentColor">
- <path d="M11.964 3.284c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036a7 7 0 1 0 8.68-8.68z"
- fill-opacity=".2"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"></path>
- <path d="M7 4a.979.979 0 0 1-1-1 1 1 0 0 0-2 0 .979.979 0 0 1-1 1 1 1 0 0 0 0 2 .979.979 0 0 1 1 1 1 1 0 0 0 2 0 .979.979 0 0 1 1-1 1 1 0 0 0 0-2z"></path>
- </g>
- </svg>
- {:else if selection === "light"}
- <svg class="icon ld-switch-btn__icon"
- viewBox="0 0 20 20">
- <title>light-auto</title>
- <g fill="currentColor">
- <path d="M10 14a4 4 0 1 1 3.465-6"
- fill-opacity=".2"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M12 18l2.5-7h1l2.5 7"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M12.714 16h4.572"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M10 1v1.5"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M16.364 3.636l-1.061 1.061"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M3.636 16.364l1.061-1.061"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M1 10h1.5"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M3.636 3.636l1.061 1.061"></path>
- </g>
- </svg>
- {:else }
- <svg class="icon ld-switch-btn__icon"
- viewBox="0 0 20 20">
- <title>dark-auto</title>
- <g fill="currentColor">
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M12 18l2.5-7h1l2.5 7"></path>
- <path fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M12.714 16h4.572"></path>
- <path d="M12.146 10.159A2.5 2.5 0 0 1 14.5 8.5h1a2.5 2.5 0 0 1 1.412.441 7 7 0 0 0-4.948-5.657c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036a6.99 6.99 0 0 0 6.427 5.012z"
- fill-opacity=".2"></path>
- <path d="M16.71 8a7.015 7.015 0 0 0-4.746-4.716c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036A7.006 7.006 0 0 0 9 16.929"
- fill="none"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"></path>
- <path d="M7 4a.979.979 0 0 1-1-1 1 1 0 0 0-2 0 .979.979 0 0 1-1 1 1 1 0 0 0 0 2 .979.979 0 0 1 1 1 1 1 0 0 0 2 0 .979.979 0 0 1 1-1 1 1 0 0 0 0-2z"></path>
- </g>
- </svg>
- {/if}
- </div>
- </button>
-</div>
-
-<div class="bg-light position-fixed margin-top-xxs padding-x-xs padding-bottom-xs padding-top-xxxs radius-md inner-glow shadow-xs"
+<div class="bg-light padding-x-xs padding-bottom-xs padding-top-xxxs radius-md {glow ? 'inner-glow shadow-xs' : ''}"
class:is-hidden={!show}
- style="right: 15px"
data-theme-switcher-element
role="listbox">
<div class="flex flex-wrap flex-column"
diff --git a/apps/web-shared/src/lib/configuration.ts b/apps/web-shared/src/lib/configuration.ts
index cb08d21..5c79089 100644
--- a/apps/web-shared/src/lib/configuration.ts
+++ b/apps/web-shared/src/lib/configuration.ts
@@ -40,7 +40,8 @@ export function is_debug(): boolean {
}
export const CookieNames = {
- theme: "go_theme"
+ theme: "go_theme",
+ locale: "go_locale"
};
export const QueryKeys = {
diff --git a/apps/web-shared/src/lib/helpers.ts b/apps/web-shared/src/lib/helpers.ts
index 4da8254..1cf94f4 100644
--- a/apps/web-shared/src/lib/helpers.ts
+++ b/apps/web-shared/src/lib/helpers.ts
@@ -1,4 +1,4 @@
-import {base_domain, CookieNames, StorageKeys} from "$shared/lib/configuration";
+import {base_domain, CookieNames} from "$shared/lib/configuration";
import {TimeEntryDto} from "$shared/lib/models/TimeEntryDto";
import {UnwrappedEntryDateTime} from "$shared/lib/models/UnwrappedEntryDateTime";
import {Temporal} from "@js-temporal/polyfill";
@@ -482,11 +482,3 @@ export function get_hash_code(value: string): number|undefined {
}
return hash;
}
-
-export function $(selector: string): HTMLElement|null {
- return document.querySelector(selector);
-}
-
-export function $$(selector: string): NodeListOf<Element> {
- return document.querySelectorAll(selector);
-}