feat: add language selector settings
This commit is contained in:
parent
e363b3f8db
commit
a767c44651
9 changed files with 78 additions and 39 deletions
27
src/client/components/LanguageSelector/LanguageSelector.tsx
Normal file
27
src/client/components/LanguageSelector/LanguageSelector.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import { useLocale } from '@/client/hooks/useLocale';
|
||||
import { LOCALE_OPTIONS, Locale } from '@/shared/internationalization/locales';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/Select';
|
||||
|
||||
export const LanguageSelector = () => {
|
||||
const { locale, changeLocale } = useLocale();
|
||||
|
||||
const onChange = (value: Locale) => {
|
||||
changeLocale(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select value={locale} defaultValue={locale} onValueChange={onChange}>
|
||||
<SelectTrigger className="mb-3" label="">
|
||||
<SelectValue placeholder="test" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LOCALE_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
};
|
1
src/client/components/LanguageSelector/index.ts
Normal file
1
src/client/components/LanguageSelector/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { LanguageSelector } from './LanguageSelector';
|
|
@ -66,7 +66,7 @@ describe('test: useLocale()', () => {
|
|||
|
||||
// assert
|
||||
await waitFor(() => {
|
||||
expect(result.current.locale).toEqual('en');
|
||||
expect(result.current.locale).toEqual('en-US');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,10 +11,12 @@ export const useLocale = () => {
|
|||
const browserLocale = typeof window !== 'undefined' ? window.navigator.language : undefined;
|
||||
|
||||
const locale = me.data?.locale || cookies.locale || browserLocale || 'en';
|
||||
const ctx = trpc.useContext();
|
||||
|
||||
const changeLocale = async (l: Locale) => {
|
||||
if (me.data) {
|
||||
await changeUserLocale.mutateAsync({ locale: l });
|
||||
await ctx.invalidate();
|
||||
}
|
||||
|
||||
setCookie(null, 'locale', l, {
|
||||
|
|
|
@ -244,7 +244,8 @@
|
|||
"apps-repo-hint": "URL to the apps repository.",
|
||||
"storage-path": "Storage path",
|
||||
"storage-path-hint": "Path to the storage directory. Keep empty for default (runtipi/app-data). Make sure it is an absolute path and that it exists",
|
||||
"submit": "Save"
|
||||
"submit": "Save",
|
||||
"user-settings-title": "User settings"
|
||||
},
|
||||
"security": {
|
||||
"tab-title": "Security",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Image from 'next/image';
|
||||
import React from 'react';
|
||||
import { LanguageSelector } from '@/components/LanguageSelector';
|
||||
import { getUrl } from '../../../../core/helpers/url-helpers';
|
||||
|
||||
interface IProps {
|
||||
|
@ -8,6 +9,9 @@ interface IProps {
|
|||
|
||||
export const AuthFormLayout: React.FC<IProps> = ({ children }) => (
|
||||
<div className="page page-center">
|
||||
<div className="position-absolute top-0 mt-3 end-0 me-1 pb-4">
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
<div className="container container-tight py-4">
|
||||
<div className="text-center mb-4">
|
||||
<Image
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { LanguageSelector } from '@/components/LanguageSelector';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { IconAdjustmentsAlt, IconUser } from '@tabler/icons-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
@ -85,31 +87,41 @@ export const SettingsForm = (props: IProps) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<form className="flex flex-col" onSubmit={handleSubmit(validate)}>
|
||||
<h2 className="text-2xl font-bold">{t('title')}</h2>
|
||||
<p className="mb-4">{t('subtitle')}</p>
|
||||
<div className="mb-3">
|
||||
<Input {...register('domain')} label={t('domain-name')} error={errors.domain?.message} placeholder="tipi.localhost" />
|
||||
<span className="text-muted">{t('domain-name-hint')}</span>
|
||||
<>
|
||||
<div className="d-flex">
|
||||
<IconUser className="me-2" />
|
||||
<h2 className="text-2xl font-bold">{t('user-settings-title')}</h2>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Input {...register('dnsIp')} label={t('dns-ip')} error={errors.dnsIp?.message} placeholder="9.9.9.9" />
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Input {...register('internalIp')} label={t('internal-ip')} error={errors.internalIp?.message} placeholder="192.168.1.100" />
|
||||
<span className="text-muted">{t('internal-ip-hint')}</span>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Input {...register('appsRepoUrl')} label={t('apps-repo')} error={errors.appsRepoUrl?.message} placeholder="https://github.com/meienberger/runtipi-appstore" />
|
||||
<span className="text-muted">{t('apps-repo-hint')}</span>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Input {...register('storagePath')} label={t('storage-path')} error={errors.storagePath?.message} placeholder="Storage path" />
|
||||
<span className="text-muted">{t('storage-path-hint')}</span>
|
||||
</div>
|
||||
<Button loading={loading} type="submit" className="btn-success">
|
||||
{t('submit')}
|
||||
</Button>
|
||||
</form>
|
||||
<LanguageSelector />
|
||||
<form className="flex flex-col mt-2" onSubmit={handleSubmit(validate)}>
|
||||
<div className="d-flex">
|
||||
<IconAdjustmentsAlt className="me-2" />
|
||||
<h2 className="text-2xl font-bold">{t('title')}</h2>
|
||||
</div>
|
||||
<p className="mb-4">{t('subtitle')}</p>
|
||||
<div className="mb-3">
|
||||
<Input {...register('domain')} label={t('domain-name')} error={errors.domain?.message} placeholder="tipi.localhost" />
|
||||
<span className="text-muted">{t('domain-name-hint')}</span>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Input {...register('dnsIp')} label={t('dns-ip')} error={errors.dnsIp?.message} placeholder="9.9.9.9" />
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Input {...register('internalIp')} label={t('internal-ip')} error={errors.internalIp?.message} placeholder="192.168.1.100" />
|
||||
<span className="text-muted">{t('internal-ip-hint')}</span>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Input {...register('appsRepoUrl')} label={t('apps-repo')} error={errors.appsRepoUrl?.message} placeholder="https://github.com/meienberger/runtipi-appstore" />
|
||||
<span className="text-muted">{t('apps-repo-hint')}</span>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Input {...register('storagePath')} label={t('storage-path')} error={errors.storagePath?.message} placeholder="Storage path" />
|
||||
<span className="text-muted">{t('storage-path-hint')}</span>
|
||||
</div>
|
||||
<Button loading={loading} type="submit" className="btn-success">
|
||||
{t('submit')}
|
||||
</Button>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -29,14 +29,6 @@ export const getMessagesPageProps: GetServerSideProps = async (ctx) => {
|
|||
const locale = getLocaleFromString(sessionLocale || cookieLocale || browserLocale || 'en');
|
||||
|
||||
const englishMessages = (await import(`../messages/en.json`)).default;
|
||||
if (locale === 'en') {
|
||||
return {
|
||||
props: {
|
||||
messages: englishMessages,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const messages = (await import(`../messages/${locale}.json`)).default;
|
||||
const mergedMessages = merge(englishMessages, messages);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export const APP_LOCALES = {
|
||||
en: 'English',
|
||||
'en-US': 'English',
|
||||
'fr-FR': 'Français',
|
||||
'ja-JP': '日本語',
|
||||
'ro-RO': 'Română',
|
||||
|
@ -10,7 +10,7 @@ export const APP_LOCALES = {
|
|||
|
||||
const FALLBACK_LOCALES = [
|
||||
{ from: 'fr', to: 'fr-FR' },
|
||||
{ from: 'en', to: 'en' },
|
||||
{ from: 'en', to: 'en-US' },
|
||||
{ from: 'ja', to: 'ja-JP' },
|
||||
{ from: 'ro', to: 'ro-RO' },
|
||||
{ from: 'ru', to: 'ru-RU' },
|
||||
|
@ -24,7 +24,7 @@ export const LOCALE_OPTIONS = Object.entries(APP_LOCALES).map(([value, label]) =
|
|||
|
||||
export const getLocaleFromString = (locale?: string): Locale => {
|
||||
if (!locale) {
|
||||
return 'en';
|
||||
return 'en-US';
|
||||
}
|
||||
|
||||
if (Locales.includes(locale)) {
|
||||
|
@ -36,5 +36,5 @@ export const getLocaleFromString = (locale?: string): Locale => {
|
|||
return fallback.to as Locale;
|
||||
}
|
||||
|
||||
return 'en';
|
||||
return 'en-US';
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue