1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 13:23:07 +01:00

feat: adds a dropdown to switch language locale (#2708)

This commit is contained in:
Amir Raminfar
2024-01-17 17:22:06 -08:00
committed by GitHub
parent ef359d339c
commit 89fe9e6b91
8 changed files with 38 additions and 23 deletions

View File

@@ -69,6 +69,7 @@ declare global {
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const lightTheme: typeof import('./stores/settings')['lightTheme']
const locale: typeof import('./stores/settings')['locale']
const logSearchContext: typeof import('./composable/logSearchContext')['logSearchContext']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const mapActions: typeof import('pinia')['mapActions']
@@ -423,6 +424,7 @@ declare module 'vue' {
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly lightTheme: UnwrapRef<typeof import('./stores/settings')['lightTheme']>
readonly locale: UnwrapRef<typeof import('./stores/settings')['locale']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
@@ -769,6 +771,7 @@ declare module '@vue/runtime-core' {
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly lightTheme: UnwrapRef<typeof import('./stores/settings')['lightTheme']>
readonly locale: UnwrapRef<typeof import('./stores/settings')['locale']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>

View File

@@ -20,14 +20,11 @@
<transition-group tag="ul" name="list" class="containers menu p-0 [&_li.menu-title]:px-0" v-else>
<li
v-for="item in menuItems"
:key="item.id"
:class="isLabel(item) ? 'menu-title' : item.state"
:data-testid="item.id"
:key="isContainer(item) ? item.id : item.keyLabel"
:class="isContainer(item) ? item.state : 'menu-title'"
:data-testid="isContainer(item) ? null : item.keyLabel"
>
<template v-if="isLabel(item)">
{{ item.label }}
</template>
<popup v-else>
<popup v-if="isContainer(item)">
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
active-class="active-primary"
@@ -51,6 +48,9 @@
<container-popup :container="item"></container-popup>
</template>
</popup>
<template v-else>
{{ $t(item.keyLabel) }}
</template>
</li>
</transition-group>
</transition>
@@ -65,8 +65,6 @@
import { Container } from "@/models/Container";
import { sessionHost } from "@/composable/storage";
const { t } = useI18n();
const store = useContainerStore();
const { activeContainers, visibleContainers, ready } = storeToRefs(store);
@@ -104,15 +102,13 @@ const groupedContainers = computed(() =>
),
);
type MenuLabel = { label: string; id: string; state: string };
const pinnedLabel = { label: t("label.pinned"), id: "pinned", state: "label" } as MenuLabel;
const allLabel = { label: t("label.containers"), id: "containers", state: "label" } as MenuLabel;
function isLabel(item: Container | MenuLabel): item is MenuLabel {
return (item as MenuLabel).label !== undefined;
function isContainer(item: any): item is Container {
return item.hasOwnProperty("image");
}
const menuItems = computed(() => {
const pinnedLabel = { keyLabel: "label.pinned" };
const allLabel = { keyLabel: "label.containers" };
if (groupedContainers.value.pinned.length > 0) {
return [pinnedLabel, ...groupedContainers.value.pinned, allLabel, ...groupedContainers.value.unpinned];
} else {

View File

@@ -1,19 +1,21 @@
import { type App } from "vue";
import { createI18n } from "vue-i18n";
import { locale } from "@/stores/settings";
import messages from "@intlify/unplugin-vue-i18n/messages";
const locale = messages?.hasOwnProperty(navigator.language) ? navigator.language : navigator.language.slice(0, 2);
const defaultLocale = messages?.hasOwnProperty(navigator.language)
? navigator.language
: navigator.language.slice(0, 2);
const i18n = createI18n({
legacy: false,
locale: locale,
locale: locale.value ?? defaultLocale,
fallbackLocale: "en",
messages,
});
export const install = (app: App) => {
app.use(i18n);
};
syncRefs(locale, i18n.global.locale, { immediate: false });
export const install = (app: App) => app.use(i18n);
export default i18n;

View File

@@ -29,6 +29,15 @@
<toggle v-model="softWrap">{{ $t("settings.soft-wrap") }}</toggle>
<labeled-input>
<template #label>
{{ $t("settings.locale") }}
</template>
<template #input>
<dropdown-menu v-model="locale" :options="availableLocales.map((l) => ({ label: l, value: l }))" />
</template>
</labeled-input>
<labeled-input>
<template #label>
{{ $t("settings.datetime-format") }}
@@ -134,9 +143,10 @@ import {
size,
smallerScrollbars,
softWrap,
locale,
} from "@/stores/settings";
const { t } = useI18n();
const { t, availableLocales } = useI18n();
setTitle(t("title.settings"));
const { latest, hasUpdate } = useReleases();

View File

@@ -14,6 +14,7 @@ export type Settings = {
softWrap: boolean;
collapseNav: boolean;
automaticRedirect: boolean;
locale: string | undefined;
};
export const DEFAULT_SETTINGS: Settings = {
search: true,
@@ -29,6 +30,7 @@ export const DEFAULT_SETTINGS: Settings = {
softWrap: true,
collapseNav: false,
automaticRedirect: true,
locale: undefined,
};
export const settings = useProfileStorage("settings", DEFAULT_SETTINGS);
@@ -46,5 +48,6 @@ export const {
menuWidth,
size,
search,
locale,
automaticRedirect,
} = toRefs(settings);

View File

@@ -34,6 +34,6 @@ test.describe("es locale", () => {
test.use({ locale: "es" });
test("translated text", async ({ page }) => {
await expect(page.getByTestId("containers")).toHaveText("Contenedores");
await expect(page.getByTestId("label.containers")).toHaveText("Contenedores");
});
});

View File

@@ -5,5 +5,5 @@ test("simple authentication", async ({ page }) => {
await page.locator('input[name="username"]').fill("admin");
await page.locator('input[name="password"]').fill("password");
await page.locator('button[type="submit"]').click();
await expect(page.getByTestId("containers")).toHaveText("Containers");
await expect(page.getByTestId("label.containers")).toHaveText("Containers");
});

View File

@@ -65,6 +65,7 @@ placeholder:
search-containers: Search containers (⌘ + k, ⌃k)
settings:
display: Display
locale: Override language
small-scrollbars: Use smaller scrollbars
show-timesamps: Show timestamps
soft-wrap: Soft wrap lines