Merge pull request #1312 from Tagaishi/translation-handling-update

This commit is contained in:
Thomas Camlong
2023-09-01 20:41:00 +02:00
committed by GitHub
15 changed files with 125 additions and 61 deletions

View File

@@ -18,7 +18,7 @@
"menu": { "menu": {
"moveUp": "Move up", "moveUp": "Move up",
"moveDown": "Move down", "moveDown": "Move down",
"addCategory": "Add category", "addCategory": "Add category {{location}}",
"addAbove": "above", "addAbove": "above",
"addBelow": "below" "addBelow": "below"
} }

View File

@@ -7,5 +7,6 @@
"popover": { "popover": {
"title": "Edit mode is enabled for <1>{{size}}</1> size", "title": "Edit mode is enabled for <1>{{size}}</1> size",
"text": "You can adjust and configure your apps now. Changes are <strong>not saved</strong> until you exit edit mode" "text": "You can adjust and configure your apps now. Changes are <strong>not saved</strong> until you exit edit mode"
} },
"unloadEvent": "Exit the edit mode to save your changes"
} }

View File

@@ -6,6 +6,7 @@
"key": "Shortcut key", "key": "Shortcut key",
"action": "Action", "action": "Action",
"keybinds": "Keybinds", "keybinds": "Keybinds",
"documentation": "Documentation",
"actions": { "actions": {
"toggleTheme": "Toggle light/dark mode", "toggleTheme": "Toggle light/dark mode",
"focusSearchBar": "Focus on search bar", "focusSearchBar": "Focus on search bar",
@@ -20,6 +21,9 @@
"i18n": "Loaded I18n translation namespaces", "i18n": "Loaded I18n translation namespaces",
"locales": "Configured I18n locales", "locales": "Configured I18n locales",
"experimental_disableEditMode": "<b>EXPERIMENTAL</b>: Disable edit mode" "experimental_disableEditMode": "<b>EXPERIMENTAL</b>: Disable edit mode"
},
"version": {
"new": "New: {{newVersion}}",
"dropdown": "Version {{newVersion}} is available! Current Version is {{currentVersion}}"
} }
} }

View File

@@ -29,9 +29,7 @@
}, },
"item": { "item": {
"validation": { "validation": {
"length100": "Length must be between 1 and 100", "length": "Length must be between {{shortest}} and {{longest}}",
"length200": "Length must be between 1 and 200",
"length400": "Length must be between 1 and 400",
"invalidLink": "Not a valid link", "invalidLink": "Not a valid link",
"errorMsg": "Did not save, because there were validation errors. Please adust your inputs" "errorMsg": "Did not save, because there were validation errors. Please adust your inputs"
}, },

View File

@@ -0,0 +1,24 @@
{
"title": "Cache cleaning",
"selector": {
"label": "Select the cache(s) to clear",
"data": {
"ping": "Ping queries",
"repositoryIcons": "Remote/Local icons",
"calendar&medias": "Medias from the Calendar",
"weather": "Weather data"
}
},
"buttons": {
"notificationTitle": "Cache Cleared",
"clearAll":{
"text": "Clear all cache",
"notificationMessage": "All cache has been cleared"
},
"clearSelect":{
"text": "Clear selected queries",
"notificationMessageSingle": "Cache for {{value}} has been cleared",
"notificationMessageMulti": "Cache for {{values}} have been cleared"
}
}
}

View File

@@ -0,0 +1,22 @@
{
"menu": {
"toggle": "Toggle edit mode",
"enable": "Enable edit mode",
"disable": "Disable edit mode"
},
"form": {
"label": "Edit password",
"message": "In order to toggle edit mode, you need to enter the password you entered in the environment variable named <Code>EDIT_MODE_PASSWORD</Code> . If it is not set, you are not able to toggle edit mode on and off.",
"submit": "Submit"
},
"notification": {
"success": {
"title": "Success",
"message": "Successfully toggled edit mode, reloading the page..."
},
"error": {
"title": "Error",
"message": "Failed to toggle edit mode, please try again."
}
}
}

View File

@@ -1,6 +1,7 @@
{ {
"title": "Search engine", "title": "Search engine",
"configurationName": "Search engine configuration", "configurationName": "Search engine configuration",
"custom": "Custom",
"tips": { "tips": {
"generalTip": "There are multiple prefixes you can use! Adding these in front of your query will filter the results. !s (Web), !t (Torrents), !y (YouTube), and !m (Media).", "generalTip": "There are multiple prefixes you can use! Adding these in front of your query will filter the results. !s (Web), !t (Torrents), !y (YouTube), and !m (Media).",
"placeholderTip": "%s can be used as a placeholder for the query." "placeholderTip": "%s can be used as a placeholder for the query."

View File

@@ -167,7 +167,7 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
variant="default" variant="default"
fullWidth fullWidth
> >
Documentation {t('layout/modals/about:documentation')}
</Button> </Button>
</Grid.Col> </Grid.Col>
@@ -203,6 +203,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
const { attributes } = usePackageAttributesStore(); const { attributes } = usePackageAttributesStore();
const { editModeEnabled } = useEditModeInformationStore(); const { editModeEnabled } = useEditModeInformationStore();
const { primaryColor } = useColorTheme(); const { primaryColor } = useColorTheme();
const { t } = useTranslation(['layout/modals/about']);
const { configVersion } = useConfigContext(); const { configVersion } = useConfigContext();
const { configs } = useConfigStore(); const { configs } = useConfigStore();
@@ -300,21 +301,23 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
transition={{ duration: 0.8, ease: 'easeInOut' }} transition={{ duration: 0.8, ease: 'easeInOut' }}
> >
<Badge color="green" variant="filled"> <Badge color="green" variant="filled">
new: {newVersionAvailable} {t('version.new',{ newVersion: newVersionAvailable})}
</Badge> </Badge>
</motion.div> </motion.div>
</HoverCard.Target> </HoverCard.Target>
<HoverCard.Dropdown> <HoverCard.Dropdown>
Version{' '} <Text>
<b> {t('version.dropdown', {currentVersion: attributes.packageVersion}).split('{{newVersion}}')[0]}
<Anchor <b>
target="_blank" <Anchor
href={`https://github.com/ajnart/homarr/releases/tag/${newVersionAvailable}`} target="_blank"
> href={`https://github.com/ajnart/homarr/releases/tag/${newVersionAvailable}`}
{newVersionAvailable} >
</Anchor> {newVersionAvailable}
</b>{' '} </Anchor>
is available ! Current version: {attributes.packageVersion} </b>
{t('version.dropdown', {currentVersion: attributes.packageVersion}).split('{{newVersion}}')[1]}
</Text>
</HoverCard.Dropdown> </HoverCard.Dropdown>
</HoverCard> </HoverCard>
)} )}

View File

@@ -48,13 +48,13 @@ export const CategoryEditMenu = ({ category }: CategoryEditMenuProps) => {
{t('menu.moveDown')} {t('menu.moveDown')}
</Menu.Item> </Menu.Item>
<Menu.Label> <Menu.Label>
{t('menu.addCategory')} {t('menu.addCategory',{location: ''})}
</Menu.Label> </Menu.Label>
<Menu.Item icon={<IconRowInsertTop size={20} />} onClick={addCategoryAbove}> <Menu.Item icon={<IconRowInsertTop size={20} />} onClick={addCategoryAbove}>
{t('menu.addCategory') + ' ' + t('menu.addAbove')} {t('menu.addCategory',{location: t('menu.addAbove')})}
</Menu.Item> </Menu.Item>
<Menu.Item icon={<IconRowInsertBottom size={20} />} onClick={addCategoryBelow}> <Menu.Item icon={<IconRowInsertBottom size={20} />} onClick={addCategoryBelow}>
{t('menu.addCategory') + ' ' + t('menu.addBelow')} {t('menu.addCategory',{location: t('menu.addBelow')})}
</Menu.Item> </Menu.Item>
</Menu.Dropdown> </Menu.Dropdown>
</Menu> </Menu>

View File

@@ -4,25 +4,29 @@ import { IconTrash } from '@tabler/icons-react';
import { useState } from 'react'; import { useState } from 'react';
import { queryClient } from '../../../tools/server/configurations/tanstack/queryClient.tool'; import { queryClient } from '../../../tools/server/configurations/tanstack/queryClient.tool';
import { useTranslation } from 'react-i18next';
const data = [
{ value: 'ping', label: 'Ping queries' },
{ value: 'repository-icons', label: 'Remote/Local icons' },
{ value: 'calendar/medias', label: 'Medais from the Calendar' },
{ value: 'weather', label: 'Weather data' },
];
export function CacheButtons() { export function CacheButtons() {
const [value, setValue] = useState<string[]>([]); const [value, setValue] = useState<string[]>([]);
const { t } = useTranslation('settings/general/cache-buttons')
const data = [
{ value: 'ping', label: t('selector.data.ping') },
{ value: 'repository-icons', label: t('selector.data.repositoryIcons') },
{ value: 'calendar/medias', label: t('selector.data.calendar&medias') },
{ value: 'weather', label: t('selector.data.weather') },
];
return ( return (
<Stack spacing="xs"> <Stack spacing="xs">
<Title order={4}>Cache cleaning</Title> <Title order={4}>{t('title')}</Title>
<MultiSelect <MultiSelect
value={value} value={value}
searchable searchable
onChange={setValue} onChange={setValue}
data={data} data={data}
label="Select the cache(s) to clear" label={t('selector.label')}
/> />
<Group> <Group>
<Button <Button
@@ -31,8 +35,11 @@ export function CacheButtons() {
onClick={() => onClick={() =>
queryClient.invalidateQueries(value).then(() => queryClient.invalidateQueries(value).then(() =>
notifications.show({ notifications.show({
title: 'Cache cleared', title: t('buttons.notificationTitle'),
message: `Cache for ${value.join(', ')} has been cleared`, message:
value.length > 1 ?
t('buttons.clearSelect.notificationMessageMulti', {values: value.join(', ')}) :
t('buttons.clearSelect.notificationMessageSingle', {value: value[0]}),
color: 'teal', color: 'teal',
icon: <IconTrash />, icon: <IconTrash />,
autoClose: 5000, autoClose: 5000,
@@ -40,14 +47,14 @@ export function CacheButtons() {
) )
} }
> >
Clear selected queries {t('buttons.clearSelect.text')}
</Button> </Button>
<Button <Button
onClick={() => onClick={() =>
queryClient.invalidateQueries().then(() => queryClient.invalidateQueries().then(() =>
notifications.show({ notifications.show({
title: 'Cache cleared', title: t('buttons.notificationTitle'),
message: 'All cache has been cleared', message: t('buttons.clearAll.notificationMessage'),
color: 'teal', color: 'teal',
icon: <IconTrash />, icon: <IconTrash />,
autoClose: 5000, autoClose: 5000,
@@ -55,7 +62,7 @@ export function CacheButtons() {
) )
} }
> >
Clear all cache {t('buttons.clearAll.text')}
</Button> </Button>
</Group> </Group>
</Stack> </Stack>

View File

@@ -35,6 +35,13 @@ export const SearchEngineSelector = ({ searchEngine }: Props) => {
updateSearchEngineConfig(engine, url); updateSearchEngineConfig(engine, url);
}; };
const searchEngineOptions: { label: string; value: EngineType }[] = [
{ label: 'Google', value: 'google' },
{ label: 'DuckDuckGo', value: 'duckDuckGo' },
{ label: 'Bing', value: 'bing' },
{ label: t('custom'), value: 'custom' },
];
return ( return (
<Stack spacing={0} mt="xs"> <Stack spacing={0} mt="xs">
<Title order={5} mb="xs"> <Title order={5} mb="xs">
@@ -77,13 +84,6 @@ export const SearchEngineSelector = ({ searchEngine }: Props) => {
); );
}; };
const searchEngineOptions: { label: string; value: EngineType }[] = [
{ label: 'Google', value: 'google' },
{ label: 'DuckDuckGo', value: 'duckDuckGo' },
{ label: 'Bing', value: 'bing' },
{ label: 'Custom', value: 'custom' },
];
export const searchUrls: { [key in CommonSearchEngineCommonSettingsType['type']]: string } = { export const searchUrls: { [key in CommonSearchEngineCommonSettingsType['type']]: string } = {
google: 'https://google.com/search?q=', google: 'https://google.com/search?q=',
duckDuckGo: 'https://duckduckgo.com/?q=', duckDuckGo: 'https://duckduckgo.com/?q=',

View File

@@ -14,8 +14,6 @@ import { useNamedWrapperColumnCount } from '../../../../Dashboard/Wrappers/grids
import { useCardStyles } from '../../../useCardStyles'; import { useCardStyles } from '../../../useCardStyles';
import { AddElementAction } from '../AddElementAction/AddElementAction'; import { AddElementAction } from '../AddElementAction/AddElementAction';
const beforeUnloadEventText = 'Exit the edit mode to save your changes';
export const ToggleEditModeAction = () => { export const ToggleEditModeAction = () => {
const { enabled, toggleEditMode } = useEditModeStore(); const { enabled, toggleEditMode } = useEditModeStore();
const namedWrapperColumnCount = useNamedWrapperColumnCount(); const namedWrapperColumnCount = useNamedWrapperColumnCount();
@@ -24,7 +22,7 @@ export const ToggleEditModeAction = () => {
namedWrapperColumnCount !== null namedWrapperColumnCount !== null
? t(`common:breakPoints.${namedWrapperColumnCount}`) ? t(`common:breakPoints.${namedWrapperColumnCount}`)
: t('common:loading'); : t('common:loading');
const beforeUnloadEventText = t('unloadEvent');
const smallerThanSm = useScreenSmallerThan('sm'); const smallerThanSm = useScreenSmallerThan('sm');
const { config } = useConfigContext(); const { config } = useConfigContext();
const { classes } = useCardStyles(true); const { classes } = useCardStyles(true);

View File

@@ -4,10 +4,12 @@ import { openModal } from '@mantine/modals';
import { showNotification } from '@mantine/notifications'; import { showNotification } from '@mantine/notifications';
import { IconEdit, IconEditOff } from '@tabler/icons-react'; import { IconEdit, IconEditOff } from '@tabler/icons-react';
import axios from 'axios'; import axios from 'axios';
import { Trans, useTranslation } from 'next-i18next';
import { useEditModeInformationStore } from '../../../../hooks/useEditModeInformation'; import { useEditModeInformationStore } from '../../../../hooks/useEditModeInformation';
function ModalContent() { function ModalContent() {
const { t } = useTranslation('settings/general/edit-mode-toggle');
const form = useForm({ const form = useForm({
initialValues: { initialValues: {
triedPassword: '', triedPassword: '',
@@ -20,8 +22,8 @@ function ModalContent() {
.post('/api/configs/tryPassword', { tried: values.triedPassword, type: 'edit' }) .post('/api/configs/tryPassword', { tried: values.triedPassword, type: 'edit' })
.then((res) => { .then((res) => {
showNotification({ showNotification({
title: 'Success', title: t('notification.success.title'),
message: 'Successfully toggled edit mode, reloading the page...', message: t('notification.success.message'),
color: 'green', color: 'green',
}); });
setTimeout(() => { setTimeout(() => {
@@ -30,8 +32,8 @@ function ModalContent() {
}) })
.catch((_) => { .catch((_) => {
showNotification({ showNotification({
title: 'Error', title: t('notification.error.title'),
message: 'Failed to toggle edit mode, please try again.', message: t('notification.error.message'),
color: 'red', color: 'red',
}); });
}); });
@@ -39,18 +41,19 @@ function ModalContent() {
> >
<Stack> <Stack>
<Text size="sm"> <Text size="sm">
In order to toggle edit mode, you need to enter the password you entered in the <Trans
environment variable named <Code>EDIT_MODE_PASSWORD</Code> . If it is not set, you are not i18nKey={'settings/general/edit-mode-toggle:form.message'}
able to toggle edit mode on and off. components={{ Code: <Code children/> }}
/>
</Text> </Text>
<PasswordInput <PasswordInput
id="triedPassword" id="triedPassword"
label="Edit password" label={t('form.label')}
autoFocus autoFocus
required required
{...form.getInputProps('triedPassword')} {...form.getInputProps('triedPassword')}
/> />
<Button type="submit">Submit</Button> <Button type="submit">{t('form.submit')}</Button>
</Stack> </Stack>
</form> </form>
); );
@@ -59,6 +62,7 @@ function ModalContent() {
export function EditModeToggle() { export function EditModeToggle() {
const { editModeEnabled } = useEditModeInformationStore(); const { editModeEnabled } = useEditModeInformationStore();
const Icon = editModeEnabled ? IconEdit : IconEditOff; const Icon = editModeEnabled ? IconEdit : IconEditOff;
const { t } = useTranslation('settings/general/edit-mode-toggle');
return ( return (
<Menu.Item <Menu.Item
@@ -66,14 +70,14 @@ export function EditModeToggle() {
icon={<Icon strokeWidth={1.2} size={18} />} icon={<Icon strokeWidth={1.2} size={18} />}
onClick={() => onClick={() =>
openModal({ openModal({
title: 'Toggle edit mode', title: t('menu.toggle'),
centered: true, centered: true,
size: 'lg', size: 'lg',
children: <ModalContent />, children: <ModalContent />,
}) })
} }
> >
{editModeEnabled ? 'Enable edit mode' : 'Disable edit mode'} {editModeEnabled ? t('menu.enable') : t('menu.disable')}
</Menu.Item> </Menu.Item>
); );
} }

View File

@@ -8,10 +8,12 @@ export const dashboardNamespaces = [
'layout/header/actions/toggle-edit-mode', 'layout/header/actions/toggle-edit-mode',
'layout/mobile/drawer', 'layout/mobile/drawer',
'settings/common', 'settings/common',
'settings/general/theme-selector', 'settings/general/cache-buttons',
'settings/general/config-changer', 'settings/general/config-changer',
'settings/general/edit-mode-toggle',
'settings/general/internationalization', 'settings/general/internationalization',
'settings/general/search-engine', 'settings/general/search-engine',
'settings/general/theme-selector',
'settings/general/widget-positions', 'settings/general/widget-positions',
'settings/customization/accessibility', 'settings/customization/accessibility',
'settings/customization/general', 'settings/customization/general',

View File

@@ -84,11 +84,11 @@ const definition = defineWidget({
return undefined; return undefined;
} }
return t('item.validation.length100'); return t('item.validation.length', {shortest: "1", longest: "100"});
}, },
href: (value) => { href: (value) => {
if (!z.string().min(1).max(200).safeParse(value).success) { if (!z.string().min(1).max(200).safeParse(value).success) {
return t('item.validation.length200'); return t('item.validation.length', {shortest: "1", longest: "200"});
} }
if (!z.string().url().safeParse(value).success) { if (!z.string().url().safeParse(value).success) {
@@ -102,7 +102,7 @@ const definition = defineWidget({
return undefined; return undefined;
} }
return t('item.validation.length400'); return t('item.validation.length', {shortest: "1", longest: "400"});
}, },
}, },
validateInputOnChange: true, validateInputOnChange: true,