mirror of
https://github.com/unraid/api.git
synced 2026-02-17 13:38:29 -06:00
chore: add dev mode language selection (#1782)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a language switcher widget to all test pages for convenient locale selection. * Displays supported language options in a dropdown menu with extensive multi-language support. * Persists user's locale preference across page reloads. * **Tests** * Enhanced test utilities with improved multi-locale support and locale switching capabilities. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,9 +1,66 @@
|
||||
import { nextTick } from 'vue';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
|
||||
import ar from '~/locales/ar.json';
|
||||
import bn from '~/locales/bn.json';
|
||||
import ca from '~/locales/ca.json';
|
||||
import cs from '~/locales/cs.json';
|
||||
import da from '~/locales/da.json';
|
||||
import de from '~/locales/de.json';
|
||||
import enUS from '~/locales/en.json';
|
||||
import es from '~/locales/es.json';
|
||||
import fr from '~/locales/fr.json';
|
||||
import hi from '~/locales/hi.json';
|
||||
import hr from '~/locales/hr.json';
|
||||
import hu from '~/locales/hu.json';
|
||||
import it from '~/locales/it.json';
|
||||
import ja from '~/locales/ja.json';
|
||||
import ko from '~/locales/ko.json';
|
||||
import lv from '~/locales/lv.json';
|
||||
import nl from '~/locales/nl.json';
|
||||
import no from '~/locales/no.json';
|
||||
import pl from '~/locales/pl.json';
|
||||
import pt from '~/locales/pt.json';
|
||||
import ro from '~/locales/ro.json';
|
||||
import ru from '~/locales/ru.json';
|
||||
import sv from '~/locales/sv.json';
|
||||
import uk from '~/locales/uk.json';
|
||||
import zh from '~/locales/zh.json';
|
||||
|
||||
import type { I18n } from 'vue-i18n';
|
||||
|
||||
const DEFAULT_LOCALE = 'en_US';
|
||||
|
||||
type LocaleMessages = typeof enUS;
|
||||
|
||||
const localeMessages: Record<string, LocaleMessages> = {
|
||||
en_US: enUS,
|
||||
ar,
|
||||
bn,
|
||||
ca,
|
||||
cs,
|
||||
da,
|
||||
de,
|
||||
es,
|
||||
fr,
|
||||
hi,
|
||||
hr,
|
||||
hu,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
lv,
|
||||
nl,
|
||||
no,
|
||||
pl,
|
||||
pt,
|
||||
ro,
|
||||
ru,
|
||||
sv,
|
||||
uk,
|
||||
zh,
|
||||
};
|
||||
|
||||
type AnyObject = Record<string, unknown>;
|
||||
|
||||
const flatMessages = enUS as unknown as Record<string, string>;
|
||||
@@ -56,3 +113,31 @@ export function createTestI18n() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function switchLocale(i18n: I18n, locale: string): Promise<void> {
|
||||
const normalizedLocale = locale === 'en' ? DEFAULT_LOCALE : locale;
|
||||
|
||||
if (!localeMessages[normalizedLocale]) {
|
||||
console.warn(
|
||||
`[switchLocale] Locale "${locale}" not available. Available locales: ${Object.keys(localeMessages).join(', ')}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const availableLocales = i18n.global.availableLocales as unknown as string[];
|
||||
|
||||
if (!availableLocales.includes(normalizedLocale)) {
|
||||
i18n.global.setLocaleMessage(
|
||||
normalizedLocale as typeof DEFAULT_LOCALE,
|
||||
localeMessages[normalizedLocale]
|
||||
);
|
||||
availableLocales.push(normalizedLocale);
|
||||
}
|
||||
|
||||
if (typeof i18n.global.locale === 'string') {
|
||||
i18n.global.locale = normalizedLocale as typeof DEFAULT_LOCALE;
|
||||
} else {
|
||||
i18n.global.locale.value = normalizedLocale as typeof DEFAULT_LOCALE;
|
||||
}
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
</style>
|
||||
|
||||
<!-- Load the manifest and inject resources -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
<script src="/test-pages/test-server-state.js"></script>
|
||||
<script src="/test-pages/shared-header.js"></script>
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<!-- Load the manifest -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
|
||||
<!-- Authentication test logic -->
|
||||
|
||||
@@ -340,6 +340,7 @@ GraphQL Endpoint: ${window.GRAPHQL_ENDPOINT || 'Not configured'}
|
||||
</script>
|
||||
|
||||
<!-- Load shared header and manifest resources -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/shared-header.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
<script src="/test-pages/test-server-state.js"></script>
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<!-- Load the manifest and inject resources (mimics PHP extractor) -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
<script src="/test-pages/test-server-state.js"></script>
|
||||
<script src="/test-pages/shared-header.js"></script>
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Load the manifest and inject resources (mimics PHP extractor) -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
172
web/public/test-pages/language-switcher.js
Normal file
172
web/public/test-pages/language-switcher.js
Normal file
@@ -0,0 +1,172 @@
|
||||
(() => {
|
||||
const localeOptions = [
|
||||
{ value: 'en_US', label: 'English (US)' },
|
||||
{ value: 'ar', label: 'العربية (Arabic)' },
|
||||
{ value: 'bn', label: 'বাংলা (Bengali)' },
|
||||
{ value: 'ca', label: 'Català (Catalan)' },
|
||||
{ value: 'cs', label: 'Čeština (Czech)' },
|
||||
{ value: 'da', label: 'Dansk (Danish)' },
|
||||
{ value: 'de', label: 'Deutsch (German)' },
|
||||
{ value: 'es', label: 'Español (Spanish)' },
|
||||
{ value: 'fr', label: 'Français (French)' },
|
||||
{ value: 'hi', label: 'हिन्दी (Hindi)' },
|
||||
{ value: 'hr', label: 'Hrvatski (Croatian)' },
|
||||
{ value: 'hu', label: 'Magyar (Hungarian)' },
|
||||
{ value: 'it', label: 'Italiano (Italian)' },
|
||||
{ value: 'ja', label: '日本語 (Japanese)' },
|
||||
{ value: 'ko', label: '한국어 (Korean)' },
|
||||
{ value: 'lv', label: 'Latviešu (Latvian)' },
|
||||
{ value: 'nl', label: 'Nederlands (Dutch)' },
|
||||
{ value: 'no', label: 'Norsk (Norwegian)' },
|
||||
{ value: 'pl', label: 'Polski (Polish)' },
|
||||
{ value: 'pt', label: 'Português (Portuguese)' },
|
||||
{ value: 'ro', label: 'Română (Romanian)' },
|
||||
{ value: 'ru', label: 'Русский (Russian)' },
|
||||
{ value: 'sv', label: 'Svenska (Swedish)' },
|
||||
{ value: 'uk', label: 'Українська (Ukrainian)' },
|
||||
{ value: 'zh', label: '中文 (Chinese)' },
|
||||
];
|
||||
|
||||
if (document.getElementById('test-language-switcher')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#test-language-switcher {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: rgba(17, 24, 39, 0.9);
|
||||
color: #f9fafb;
|
||||
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.35);
|
||||
z-index: 9999;
|
||||
max-width: 240px;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
#test-language-switcher label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
#test-language-switcher select {
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.4);
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
color: #f9fafb;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#test-language-switcher select:focus {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
#test-language-switcher .info {
|
||||
font-size: 12px;
|
||||
color: rgba(226, 232, 240, 0.8);
|
||||
line-height: 1.4;
|
||||
}
|
||||
`;
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.id = 'test-language-switcher';
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.htmlFor = 'test-language-select';
|
||||
label.textContent = 'Language';
|
||||
|
||||
const select = document.createElement('select');
|
||||
select.id = 'test-language-select';
|
||||
|
||||
const STORAGE_KEY = 'unraid:test:locale';
|
||||
const availableLocales = new Set(localeOptions.map((option) => option.value));
|
||||
|
||||
const readPersistedLocale = () => {
|
||||
let persisted;
|
||||
try {
|
||||
persisted = window.localStorage?.getItem(STORAGE_KEY) ?? undefined;
|
||||
} catch {
|
||||
persisted = undefined;
|
||||
}
|
||||
return persisted;
|
||||
};
|
||||
|
||||
const resolveInitialLocale = () => {
|
||||
const candidates = [
|
||||
typeof window.LOCALE === 'string' ? window.LOCALE : undefined,
|
||||
readPersistedLocale(),
|
||||
'en_US',
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (candidate && availableLocales.has(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return 'en_US';
|
||||
};
|
||||
|
||||
const initialLocale = resolveInitialLocale();
|
||||
window.LOCALE = initialLocale;
|
||||
let currentLocale = initialLocale;
|
||||
|
||||
localeOptions.forEach((option) => {
|
||||
const optionElement = document.createElement('option');
|
||||
optionElement.value = option.value;
|
||||
optionElement.textContent = option.label;
|
||||
if (option.value === currentLocale) {
|
||||
optionElement.selected = true;
|
||||
}
|
||||
select.appendChild(optionElement);
|
||||
});
|
||||
|
||||
select.addEventListener('change', (event) => {
|
||||
const nextLocale = event.target.value;
|
||||
if (nextLocale === currentLocale) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.localStorage?.setItem(STORAGE_KEY, nextLocale);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
window.LOCALE = nextLocale;
|
||||
currentLocale = nextLocale;
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
const info = document.createElement('div');
|
||||
info.className = 'info';
|
||||
info.textContent = 'Reloads the page to load components in the selected locale.';
|
||||
|
||||
container.appendChild(label);
|
||||
container.appendChild(select);
|
||||
container.appendChild(info);
|
||||
|
||||
const attach = () => {
|
||||
if (!document.head.contains(style)) {
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
if (!document.body.contains(container)) {
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', attach, { once: true });
|
||||
} else {
|
||||
attach();
|
||||
}
|
||||
})();
|
||||
@@ -221,6 +221,7 @@
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<!-- Load manifest -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
|
||||
<!-- Test Logic -->
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<!-- Load the manifest and inject resources (mimics PHP extractor) -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
<script src="/test-pages/test-server-state.js"></script>
|
||||
<script src="/test-pages/shared-header.js"></script>
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<!-- Load the manifest and inject resources -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user