diff --git a/package.json b/package.json
index 4d5538520..86fa44b8d 100644
--- a/package.json
+++ b/package.json
@@ -72,8 +72,6 @@
"i18next": "^22.5.1",
"immer": "^10.0.2",
"js-file-download": "^0.4.12",
- "moment": "^2.29.4",
- "moment-timezone": "^0.5.43",
"next": "13.4.10",
"next-i18next": "^13.0.0",
"nzbget-api": "^0.0.3",
@@ -230,4 +228,4 @@
]
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/layout/common.json b/public/locales/en/layout/common.json
index f06543f33..b676bd888 100644
--- a/public/locales/en/layout/common.json
+++ b/public/locales/en/layout/common.json
@@ -14,5 +14,12 @@
"category": {
"openAllInNewTab": "Open all in new tab"
}
+ },
+ "menu": {
+ "moveUp": "Move up",
+ "moveDown": "Move down",
+ "addCategory": "Add category",
+ "addAbove": "above",
+ "addBelow": "below"
}
}
\ No newline at end of file
diff --git a/public/locales/en/layout/element-selector/selector.json b/public/locales/en/layout/element-selector/selector.json
index 7f7b0c778..3d2b934b0 100644
--- a/public/locales/en/layout/element-selector/selector.json
+++ b/public/locales/en/layout/element-selector/selector.json
@@ -7,5 +7,19 @@
"goBack": "Go back to the previous step",
"actionIcon": {
"tooltip": "Add a tile"
+ },
+ "apps": "Apps",
+ "app": {
+ "defaultName": "Your App"
+ },
+ "widgets": "Widgets",
+ "categories": "Categories",
+ "category": {
+ "newName": "Name of new category",
+ "defaultName": "New Category",
+ "created": {
+ "title": "Category created",
+ "message": "The category \"{{name}}\" has been created"
+ }
}
}
diff --git a/public/locales/en/layout/modals/about.json b/public/locales/en/layout/modals/about.json
index 83109b624..01a8f5d09 100644
--- a/public/locales/en/layout/modals/about.json
+++ b/public/locales/en/layout/modals/about.json
@@ -6,6 +6,12 @@
"key": "Shortcut key",
"action": "Action",
"keybinds": "Keybinds",
+ "actions": {
+ "toggleTheme": "Toggle light/dark mode",
+ "focusSearchBar": "Focus on search bar",
+ "openDocker": "Open docker Widget",
+ "toggleEdit": "Toggle Edit Mode"
+ },
"metrics": {
"configurationSchemaVersion": "Configuration schema version",
"configurationsCount": "Available configurations",
@@ -15,4 +21,5 @@
"locales": "Configured I18n locales",
"experimental_disableEditMode": "EXPERIMENTAL: Disable edit mode"
}
+
}
\ No newline at end of file
diff --git a/public/locales/en/layout/modals/add-app.json b/public/locales/en/layout/modals/add-app.json
index 6a7670617..683ba8724 100644
--- a/public/locales/en/layout/modals/add-app.json
+++ b/public/locales/en/layout/modals/add-app.json
@@ -99,6 +99,12 @@
}
},
"validation": {
- "popover": "Your form contains invalid data. Hence, it can't be saved. Please resolve all issues and click this button again to save your changes"
+ "popover": "Your form contains invalid data. Hence, it can't be saved. Please resolve all issues and click this button again to save your changes",
+ "name": "Name is required",
+ "noUrl": "Url is required",
+ "invalidUrl": "Value is not a valid url",
+ "noIconUrl": "This field is required",
+ "noExternalUri": "External URI is required",
+ "invalidExternalUri": "External URI is not a valid uri"
}
}
diff --git a/public/locales/en/modules/bookmark.json b/public/locales/en/modules/bookmark.json
index 183ee3130..43ede12c2 100644
--- a/public/locales/en/modules/bookmark.json
+++ b/public/locales/en/modules/bookmark.json
@@ -12,7 +12,12 @@
"label": "Items"
},
"layout": {
- "label": "Layout"
+ "label": "Layout",
+ "data":{
+ "autoGrid": "Auto Grid",
+ "horizontal": "Horizontal",
+ "vertical": "Vertical"
+ }
}
}
},
@@ -21,5 +26,21 @@
"title": "Bookmark list empty",
"text": "Add new items to this list in the edit mode"
}
+ },
+ "item": {
+ "validation": {
+ "length100": "Length must be between 1 and 100",
+ "length200": "Length must be between 1 and 200",
+ "length400": "Length must be between 1 and 400",
+ "invalidLink": "Not a valid link",
+ "errorMsg": "Did not save, because there were validation errors. Please adust your inputs"
+ },
+ "name": "Name",
+ "url": "URL",
+ "newTab": "Open in new tab",
+ "hideHostname": "Hide Hostname",
+ "hideIcon": "Hide Icon",
+ "delete": "Delete"
+
}
}
diff --git a/public/locales/en/modules/calendar.json b/public/locales/en/modules/calendar.json
index 8fcfdf442..1f261ade0 100644
--- a/public/locales/en/modules/calendar.json
+++ b/public/locales/en/modules/calendar.json
@@ -11,13 +11,25 @@
"label": "Start the week on Sunday"
},
"radarrReleaseType": {
- "label": "Radarr release type"
+ "label": "Radarr release type",
+ "data":{
+ "inCinemas": "In Cinemas",
+ "physicalRelease": "Physical",
+ "digitalRelease": "Digital"
+ }
},
"hideWeekDays": {
"label": "Hide week days"
},
"fontSize": {
- "label": "Font Size"
+ "label": "Font Size",
+ "data":{
+ "xs": "Extra Small",
+ "sm": "Small",
+ "md": "Medium",
+ "lg": "Large",
+ "xl": "Extra Large"
+ }
}
}
}
diff --git a/public/locales/en/modules/iframe.json b/public/locales/en/modules/iframe.json
index 067cda28a..baa55e498 100644
--- a/public/locales/en/modules/iframe.json
+++ b/public/locales/en/modules/iframe.json
@@ -38,7 +38,8 @@
"noUrl": {
"title": "Invalid URL",
"text": "Ensure that you've entered a valid address in the configuration of your widget"
- }
+ },
+ "browserSupport": "Your Browser does not support iframes. Please update your browser."
}
}
}
diff --git a/public/locales/en/modules/media-requests-list.json b/public/locales/en/modules/media-requests-list.json
index a1a2b6ab2..9016a0f0f 100644
--- a/public/locales/en/modules/media-requests-list.json
+++ b/public/locales/en/modules/media-requests-list.json
@@ -19,6 +19,37 @@
},
"tooltips": {
"approve": "Approve requests",
- "decline": "Decline requests"
+ "decline": "Decline requests",
+ "approving": "Approving Request..."
+ },
+ "mutation": {
+ "approving": "Approving",
+ "declining": "Declining",
+ "request": "request...",
+ "approved": "Request was approved!",
+ "declined": "Request was declined!"
+ },
+ "detail": {
+ "label": "Stats for nerds",
+ "id": "ID",
+ "device": "Device",
+ "video": {
+ "video":"Video",
+ "resolution": "Resolution",
+ "framerate": "Framerate",
+ "codec": "Video Codec"
+ },
+ "audio": {
+ "audio": "Audio",
+ "channels": "Audio Channels",
+ "codec": "Audio Codec"
+ },
+ "transcoding": {
+ "transcoding": "Transcoding",
+ "context": "Context",
+ "requested": "Hardware Encoding Requested",
+ "source": "Source Codec",
+ "target": "Target Codec"
+ }
}
}
diff --git a/public/locales/en/modules/media-requests-stats.json b/public/locales/en/modules/media-requests-stats.json
index a06a52860..3027c8e1c 100644
--- a/public/locales/en/modules/media-requests-stats.json
+++ b/public/locales/en/modules/media-requests-stats.json
@@ -5,7 +5,11 @@
"settings": {
"title": "Media requests stats",
"direction": {
- "label": "Direction of the layout."
+ "label": "Direction of the layout.",
+ "data":{
+ "row": "Horizontal",
+ "column": "Vertical"
+ }
}
}
},
diff --git a/public/locales/en/modules/media-server.json b/public/locales/en/modules/media-server.json
index 1b93eb4c2..4dd399283 100644
--- a/public/locales/en/modules/media-server.json
+++ b/public/locales/en/modules/media-server.json
@@ -6,6 +6,7 @@
"title": "Settings for media server widget"
}
},
+ "loading": "Loading streams",
"card": {
"table": {
"header": {
diff --git a/public/locales/en/modules/rss.json b/public/locales/en/modules/rss.json
index fc6907fb7..ee73f375b 100644
--- a/public/locales/en/modules/rss.json
+++ b/public/locales/en/modules/rss.json
@@ -12,7 +12,8 @@
"label": "Refresh interval (in minutes)"
},
"dangerousAllowSanitizedItemContent": {
- "label": ""
+ "label": "Allow HTML formatting (Dangerous)",
+ "info": "Allowing HTML formatting from outside could be dangerous.
Please make sure that the feed is from a trusted source."
},
"textLinesClamp": {
"label": "Text lines clamp"
diff --git a/public/locales/en/modules/torrents-status.json b/public/locales/en/modules/torrents-status.json
index cd2674b4d..3f83a6396 100644
--- a/public/locales/en/modules/torrents-status.json
+++ b/public/locales/en/modules/torrents-status.json
@@ -59,11 +59,12 @@
},
"generic": {
"title": "An unexpected error occurred",
- "text": "Homarr was unable to communicate with your Torrent clients. Please check your configuration"
+ "text": "Unable to communicate with your Torrent clients. Please check your configuration"
}
},
"loading": {
- "title": "Loading..."
+ "title": "Loading",
+ "description": "Establishing a connection"
},
"popover": {
"introductionPrefix": "Managed by",
diff --git a/public/locales/en/modules/usenet.json b/public/locales/en/modules/usenet.json
index d21c37ff4..e1b1060c6 100644
--- a/public/locales/en/modules/usenet.json
+++ b/public/locales/en/modules/usenet.json
@@ -46,4 +46,4 @@
},
"paused": "Paused"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/en/modules/weather.json b/public/locales/en/modules/weather.json
index 16b034320..a8e5cefcc 100644
--- a/public/locales/en/modules/weather.json
+++ b/public/locales/en/modules/weather.json
@@ -32,5 +32,6 @@
"thunderstormWithHail": "Thunderstorm with hail",
"unknown": "Unknown"
}
- }
+ },
+ "error": "An error occured"
}
diff --git a/public/locales/en/settings/customization/color-selector.json b/public/locales/en/settings/customization/color-selector.json
index 19f42e95b..c0555e249 100644
--- a/public/locales/en/settings/customization/color-selector.json
+++ b/public/locales/en/settings/customization/color-selector.json
@@ -1,4 +1,6 @@
{
"colors": "Colors",
- "suffix": "{{color}} color"
+ "suffix": "{{color}} color",
+ "primary": "Primary",
+ "secondary": "Secondary"
}
\ No newline at end of file
diff --git a/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx b/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx
index c5c70e641..64174046d 100644
--- a/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx
+++ b/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx
@@ -55,10 +55,10 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
const { t } = useTranslation(['common', 'layout/modals/about']);
const keybinds = [
- { key: 'Mod + J', shortcut: 'Toggle light/dark mode' },
- { key: 'Mod + K', shortcut: 'Focus on search bar' },
- { key: 'Mod + B', shortcut: 'Open docker widget' },
- { key: 'Mod + E', shortcut: 'Toggle Edit mode' },
+ { key: 'Mod + J', shortcut: t('layout/modals/about:actions.toggleTheme') },
+ { key: 'Mod + K', shortcut: t('layout/modals/about:actions.focusSearchBar') },
+ { key: 'Mod + B', shortcut: t('layout/modals/about:actions.openDocker') },
+ { key: 'Mod + E', shortcut: t('layout/modals/about:actions.toggleEdit') },
];
const rows = keybinds.map((element) => (
diff --git a/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx b/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx
index 34ab62500..782cc047e 100644
--- a/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx
+++ b/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx
@@ -47,14 +47,14 @@ export const EditAppModal = ({
const form = useForm({
initialValues: innerProps.app,
validate: {
- name: (name) => (!name ? 'Name is required' : null),
+ name: (name) => (!name ? t('validation.name') : null),
url: (url) => {
if (!url) {
- return 'Url is required';
+ return t('validation.noUrl');
}
if (!url.match(appUrlRegex)) {
- return 'Value is not a valid url';
+ return t('validation.invalidUrl');
}
return null;
@@ -62,7 +62,7 @@ export const EditAppModal = ({
appearance: {
iconUrl: (url: string) => {
if (url.length < 1) {
- return 'This field is required';
+ return t('validation.noIconUrl');
}
return null;
@@ -71,11 +71,11 @@ export const EditAppModal = ({
behaviour: {
externalUrl: (url: string) => {
if (url === undefined || url.length < 1) {
- return 'External URI is required';
+ return t('validation.noExternalUri');
}
if (!url.match(appUrlWithAnyProtocolRegex)) {
- return 'External URI is not a valid uri';
+ return t('validation.invalidExternalUri');
}
return null;
diff --git a/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx b/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx
index eb99dbcaa..3f386ff14 100644
--- a/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx
+++ b/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx
@@ -33,12 +33,12 @@ export const AvailableElementTypes = ({
const onClickCreateCategory = async () => {
openContextModalGeneric({
modal: 'categoryEditModal',
- title: 'Name of new category',
+ title: t('category.newName'),
withCloseButton: false,
innerProps: {
category: {
id: uuidv4(),
- name: 'New category',
+ name: t('category.defaultName'),
position: 0, // doesn't matter, is being overwritten
},
onSuccess: async (category) => {
@@ -65,8 +65,8 @@ export const AvailableElementTypes = ({
})).then(() => {
closeModal(modalId);
showNotification({
- title: 'Category created',
- message: `The category ${category.name} has been created`,
+ title: t('category.created.title'),
+ message: t('category.created.message', { name: category.name}),
color: 'teal',
});
});
@@ -81,7 +81,7 @@ export const AvailableElementTypes = ({
}
onClick={() => {
openContextModalGeneric<{ app: AppType; allowAppNamePropagation: boolean }>({
@@ -89,7 +89,7 @@ export const AvailableElementTypes = ({
innerProps: {
app: {
id: uuidv4(),
- name: 'Your app',
+ name: t('app.defaultName'),
url: 'https://homarr.dev',
appearance: {
iconUrl: '/imgs/logo/logo.png',
@@ -126,12 +126,12 @@ export const AvailableElementTypes = ({
}}
/>
}
onClick={onOpenWidgets}
/>
}
onClick={onClickCreateCategory}
/>
diff --git a/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx b/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx
index fa279766b..2f5557088 100644
--- a/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx
+++ b/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx
@@ -12,6 +12,7 @@ import {
import { useConfigContext } from '../../../../config/provider';
import { CategoryType } from '../../../../types/category';
import { useCategoryActions } from './useCategoryActions';
+import { useTranslation } from 'next-i18next';
interface CategoryEditMenuProps {
category: CategoryType;
@@ -21,6 +22,7 @@ export const CategoryEditMenu = ({ category }: CategoryEditMenuProps) => {
const { name: configName } = useConfigContext();
const { addCategoryAbove, addCategoryBelow, moveCategoryUp, moveCategoryDown, edit, remove } =
useCategoryActions(configName, category);
+ const { t } = useTranslation(['layout/common','common']);
return (
diff --git a/src/components/Settings/Customization/Theme/ColorSelector.tsx b/src/components/Settings/Customization/Theme/ColorSelector.tsx
index c3ae8aae2..5c3941ace 100644
--- a/src/components/Settings/Customization/Theme/ColorSelector.tsx
+++ b/src/components/Settings/Customization/Theme/ColorSelector.tsx
@@ -99,9 +99,7 @@ export function ColorSelector({ type, defaultValue }: ColorControlProps) {
- {t('suffix', {
- color: type[0].toUpperCase() + type.slice(1),
- })}
+ {t('suffix', {color: t(type)})}
);
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index fc9cfda5a..e0afe4192 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -49,6 +49,6 @@ export async function getStaticProps({ req, res, locale }: GetServerSidePropsCon
const useStyles = createStyles(() => ({
image: {
margin: '0 auto',
- display: 'blcok',
+ display: 'block',
},
}));
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index bcfcf4e42..c62bbbee4 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -8,7 +8,9 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
import Consola from 'consola';
import { getCookie } from 'cookies-next';
-import moment from 'moment-timezone';
+import dayjs from 'dayjs';
+import locale from 'dayjs/plugin/localeData'
+import utc from 'dayjs/plugin/utc'
import { GetServerSidePropsContext } from 'next';
import { appWithTranslation } from 'next-i18next';
import { AppProps } from 'next/app';
@@ -39,6 +41,9 @@ import {
} from '../tools/server/getPackageVersion';
import { theme } from '../tools/server/theme/theme';
+dayjs.extend(locale);
+dayjs.extend(utc);
+
function App(
this: any,
props: AppProps<{
@@ -54,8 +59,8 @@ function App(
const { Component, pageProps } = props;
// TODO: make mapping from our locales to moment locales
const language = getLanguageByCode(pageProps.locale);
- require('moment/locale/' + language.momentLocale);
- moment.locale(language.momentLocale);
+ require(`dayjs/locale/${language.locale}.js`);
+ dayjs.locale(language.locale);
const [primaryColor, setPrimaryColor] = useState(
props.pageProps.config?.settings.customization.colors.primary || 'red'
diff --git a/src/tools/language.ts b/src/tools/language.ts
index 10978d707..e885e1500 100644
--- a/src/tools/language.ts
+++ b/src/tools/language.ts
@@ -3,7 +3,7 @@ export type Language = {
originalName: string;
translatedName: string;
emoji: string;
- momentLocale: string;
+ locale: string;
};
export const languages: Language[] = [
@@ -12,14 +12,14 @@ export const languages: Language[] = [
originalName: 'Deutsch',
translatedName: 'German',
emoji: '🇩🇪',
- momentLocale: 'de',
+ locale: 'de',
},
{
shortName: 'en',
originalName: 'English',
translatedName: 'English',
emoji: '🇬🇧',
- momentLocale: 'en-gb',
+ locale: 'en-gb',
},
// Danish
{
@@ -27,7 +27,7 @@ export const languages: Language[] = [
originalName: 'Dansk',
translatedName: 'Danish',
emoji: '🇩🇰',
- momentLocale: 'da',
+ locale: 'da',
},
// Hebrew
{
@@ -35,49 +35,49 @@ export const languages: Language[] = [
originalName: 'עברית',
translatedName: 'Hebrew',
emoji: '🇮🇱',
- momentLocale: 'he',
+ locale: 'he',
},
{
shortName: 'es',
originalName: 'Español',
translatedName: 'Spanish',
emoji: '🇪🇸',
- momentLocale: 'es',
+ locale: 'es',
},
{
shortName: 'fr',
originalName: 'Français',
translatedName: 'French',
emoji: '🇫🇷',
- momentLocale: 'fr',
+ locale: 'fr',
},
{
shortName: 'it',
originalName: 'Italiano',
translatedName: 'Italian',
emoji: '🇮🇹',
- momentLocale: 'it',
+ locale: 'it',
},
{
shortName: 'ja',
originalName: '日本語',
translatedName: 'Japanese',
emoji: '🇯🇵',
- momentLocale: 'ja',
+ locale: 'ja',
},
{
shortName: 'ko',
originalName: '한국어',
translatedName: 'Korean',
emoji: '🇰🇷',
- momentLocale: 'ko',
+ locale: 'ko',
},
{
shortName: 'lol',
originalName: 'LOLCAT',
translatedName: 'LOLCAT',
emoji: '🐱',
- momentLocale: 'en-gb',
+ locale: 'en-gb',
},
// Norwegian
{
@@ -85,7 +85,7 @@ export const languages: Language[] = [
originalName: 'Norsk',
translatedName: 'Norwegian',
emoji: '🇳🇴',
- momentLocale: 'nb',
+ locale: 'nb',
},
// Slovak
{
@@ -93,42 +93,42 @@ export const languages: Language[] = [
originalName: 'Slovenčina',
translatedName: 'Slovak',
emoji: '🇸🇰',
- momentLocale: 'sk',
+ locale: 'sk',
},
{
shortName: 'nl',
originalName: 'Nederlands',
translatedName: 'Dutch',
emoji: '🇳🇱',
- momentLocale: 'nl',
+ locale: 'nl',
},
{
shortName: 'pl',
originalName: 'Polski',
translatedName: 'Polish',
emoji: '🇵🇱',
- momentLocale: 'pl',
+ locale: 'pl',
},
{
shortName: 'pt',
originalName: 'Português',
translatedName: 'Portuguese',
emoji: '🇵🇹',
- momentLocale: 'pt',
+ locale: 'pt',
},
{
shortName: 'ru',
originalName: 'Русский',
translatedName: 'Russian',
emoji: '🇷🇺',
- momentLocale: 'ru',
+ locale: 'ru',
},
{
shortName: 'sl',
originalName: 'Slovenščina',
translatedName: 'Slovenian',
emoji: '🇸🇮',
- momentLocale: 'sl',
+ locale: 'sl',
},
{
@@ -136,14 +136,14 @@ export const languages: Language[] = [
originalName: 'Svenska',
translatedName: 'Swedish',
emoji: '🇸🇪',
- momentLocale: 'sv',
+ locale: 'sv',
},
{
shortName: 'uk',
originalName: 'Українська',
translatedName: 'Ukrainian',
emoji: '🇺🇦',
- momentLocale: 'uk',
+ locale: 'uk',
},
// Vietnamese
{
@@ -151,42 +151,42 @@ export const languages: Language[] = [
originalName: 'Tiếng Việt',
translatedName: 'Vietnamese',
emoji: '🇻🇳',
- momentLocale: 'vi',
+ locale: 'vi',
},
{
shortName: 'zh',
originalName: '中文',
translatedName: 'Chinese',
emoji: '🇨🇳',
- momentLocale: 'zh-cn',
+ locale: 'zh-cn',
},
{
shortName: 'el',
originalName: 'Ελληνικά',
translatedName: 'Greek',
emoji: '🇬🇷',
- momentLocale: 'el',
+ locale: 'el',
},
{
shortName: 'tr',
originalName: 'Türkçe',
translatedName: 'Turkish',
emoji: '🇹🇷',
- momentLocale: 'tr',
+ locale: 'tr',
},
{
shortName: 'lv',
originalName: 'Latvian',
translatedName: 'Latvian',
emoji: '🇱🇻',
- momentLocale: 'lv',
+ locale: 'lv',
},
{
shortName: 'hr',
originalName: 'Hrvatski',
translatedName: 'Croatian',
emoji: '🇭🇷',
- momentLocale: 'hr',
+ locale: 'hr',
},
];
diff --git a/src/widgets/bookmark/BookmarkWidgetTile.tsx b/src/widgets/bookmark/BookmarkWidgetTile.tsx
index dd04b87ae..6969bc18d 100644
--- a/src/widgets/bookmark/BookmarkWidgetTile.tsx
+++ b/src/widgets/bookmark/BookmarkWidgetTile.tsx
@@ -74,6 +74,7 @@ const definition = defineWidget({
};
},
itemComponent({ data, onChange, delete: deleteData }) {
+ const { t } = useTranslation('modules/bookmark');
const form = useForm({
initialValues: data,
validate: {
@@ -83,15 +84,15 @@ const definition = defineWidget({
return undefined;
}
- return 'Length must be between 1 and 100';
+ return t('item.validation.length100');
},
href: (value) => {
if (!z.string().min(1).max(200).safeParse(value).success) {
- return 'Length must be between 1 and 200';
+ return t('item.validation.length200');
}
if (!z.string().url().safeParse(value).success) {
- return 'Not a valid link';
+ return t('item.validation.invalidLink');
}
return undefined;
@@ -101,7 +102,7 @@ const definition = defineWidget({
return undefined;
}
- return 'Length must be between 1 and 100';
+ return t('item.validation.length400');
},
},
validateInputOnChange: true,
@@ -122,13 +123,13 @@ const definition = defineWidget({
}
{...form.getInputProps('name')}
- label="Name"
+ label={t('item.name')}
withAsterisk
/>
}
{...form.getInputProps('href')}
- label="URL"
+ label={t('item.url')}
withAsterisk
/>
{!form.isValid() && (
}>
- Did not save, because there were validation errors. Please adust your inputs
+ {t('item.validation.errorMsg')}
)}
@@ -174,18 +175,9 @@ const definition = defineWidget({
layout: {
type: 'select',
data: [
- {
- label: 'Auto Grid',
- value: 'autoGrid',
- },
- {
- label: 'Horizontal',
- value: 'horizontal',
- },
- {
- label: 'Vertical',
- value: 'vertical',
- },
+ { value: 'autoGrid', },
+ { value: 'horizontal', },
+ { value: 'vertical', },
],
defaultValue: 'autoGrid',
},
@@ -206,10 +198,10 @@ interface BookmarkWidgetTileProps {
}
function BookmarkWidgetTile({ widget }: BookmarkWidgetTileProps) {
- const { t } = useTranslation('modules/bookmark');
const { classes } = useStyles();
const { enabled: isEditModeEnabled } = useEditModeStore();
const { fn, colors, colorScheme } = useMantineTheme();
+ const { t } = useTranslation('modules/bookmark');
if (widget.properties.items.length === 0) {
return (
diff --git a/src/widgets/calendar/CalendarTile.tsx b/src/widgets/calendar/CalendarTile.tsx
index 501f5a4cd..bf1016968 100644
--- a/src/widgets/calendar/CalendarTile.tsx
+++ b/src/widgets/calendar/CalendarTile.tsx
@@ -1,8 +1,9 @@
import { useMantineTheme } from '@mantine/core';
import { Calendar } from '@mantine/dates';
import { IconCalendarTime } from '@tabler/icons-react';
-import { i18n } from 'next-i18next';
import { useState } from 'react';
+import { useRouter } from 'next/router';
+import { getLanguageByCode } from '~/tools/language';
import { api } from '~/utils/api';
import { useEditModeStore } from '../../components/Dashboard/Views/useEditModeStore';
@@ -33,20 +34,20 @@ const definition = defineWidget({
type: 'select',
defaultValue: 'inCinemas',
data: [
- { label: 'In Cinemas', value: 'inCinemas' },
- { label: 'Physical', value: 'physicalRelease' },
- { label: 'Digital', value: 'digitalRelease' },
+ { value: 'inCinemas' },
+ { value: 'physicalRelease' },
+ { value: 'digitalRelease' },
],
},
fontSize: {
type: 'select',
defaultValue: 'xs',
data: [
- { label: 'Extra Small', value: 'xs' },
- { label: 'Small', value: 'sm' },
- { label: 'Medium', value: 'md' },
- { label: 'Large', value: 'lg' },
- { label: 'Extra Large', value: 'xl' },
+ { value: 'xs' },
+ { value: 'sm' },
+ { value: 'md' },
+ { value: 'lg' },
+ { value: 'xl' },
],
},
},
@@ -66,11 +67,15 @@ interface CalendarTileProps {
}
function CalendarTile({ widget }: CalendarTileProps) {
+ const { locale } = useRouter();
const { colorScheme, radius } = useMantineTheme();
const { name: configName } = useConfigContext();
const [month, setMonth] = useState(new Date());
const isEditMode = useEditModeStore((x) => x.enabled);
+ const language = getLanguageByCode(locale ?? 'en');
+ require(`dayjs/locale/${language.locale}.js`);
+
const { data: medias } = api.calendar.medias.useQuery(
{
configName: configName!,
@@ -90,7 +95,7 @@ function CalendarTile({ widget }: CalendarTileProps) {
onPreviousMonth={setMonth}
onNextMonth={setMonth}
size={widget.properties.fontSize}
- locale={i18n?.resolvedLanguage ?? 'en'}
+ locale={language.locale}
firstDayOfWeek={widget.properties.sundayStart ? 0 : 1}
hideWeekdays={widget.properties.hideWeekDays}
style={{ position: 'relative' }}
diff --git a/src/widgets/date/DateTile.tsx b/src/widgets/date/DateTile.tsx
index c9ea0d645..faa479ed9 100644
--- a/src/widgets/date/DateTile.tsx
+++ b/src/widgets/date/DateTile.tsx
@@ -1,16 +1,21 @@
import { Stack, Text, createStyles } from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import { IconClock } from '@tabler/icons-react';
-import moment from 'moment-timezone';
import { useRouter } from 'next/router';
import { useEffect, useRef, useState } from 'react';
import { getLanguageByCode } from '~/tools/language';
import { api } from '~/utils/api';
+import dayjs from 'dayjs';
+import timezones from 'dayjs/plugin/timezone'
+import utc from 'dayjs/plugin/utc'
import { useSetSafeInterval } from '../../hooks/useSetSafeInterval';
import { defineWidget } from '../helper';
import { IWidget } from '../widgets';
+dayjs.extend(utc);
+dayjs.extend(timezones);
+
const definition = defineWidget({
id: 'date',
icon: IconClock,
@@ -24,14 +29,14 @@ const definition = defineWidget({
defaultValue: 'dddd, MMMM D',
data: () => [
{ value: 'hide' },
- { value: 'dddd, MMMM D', label: moment().format('dddd, MMMM D') },
- { value: 'dddd, D MMMM', label: moment().format('dddd, D MMMM') },
- { value: 'MMM D', label: moment().format('MMM D') },
- { value: 'D MMM', label: moment().format('D MMM') },
- { value: 'DD/MM/YYYY', label: moment().format('DD/MM/YYYY') },
- { value: 'MM/DD/YYYY', label: moment().format('MM/DD/YYYY') },
- { value: 'DD/MM', label: moment().format('DD/MM') },
- { value: 'MM/DD', label: moment().format('MM/DD') },
+ { value: 'dddd, MMMM D', label: dayjs().format('dddd, MMMM D') },
+ { value: 'dddd, D MMMM', label: dayjs().format('dddd, D MMMM') },
+ { value: 'MMM D', label: dayjs().format('MMM D') },
+ { value: 'D MMM', label: dayjs().format('D MMM') },
+ { value: 'DD/MM/YYYY', label: dayjs().format('DD/MM/YYYY') },
+ { value: 'MM/DD/YYYY', label: dayjs().format('MM/DD/YYYY') },
+ { value: 'DD/MM', label: dayjs().format('DD/MM') },
+ { value: 'MM/DD', label: dayjs().format('MM/DD') },
],
},
enableTimezone: {
@@ -84,11 +89,11 @@ function DateTile({ widget }: DateTileProps) {
className={cx(classes.extras, 'dashboard-tile-clock-city')}
>
{widget.properties.timezoneLocation.name}
- {widget.properties.titleState === 'both' && moment(date).format(' (z)')}
+ {widget.properties.titleState === 'both' && dayjs(date).format(' (z)')}
)}
- {moment(date).format(formatString)}
+ {dayjs(date).format(formatString)}
{!widget.properties.dateFormat.includes('hide') && (
- {moment(date).format(widget.properties.dateFormat)}
+ {dayjs(date).format(widget.properties.dateFormat)}
)}
@@ -139,7 +144,7 @@ const useDateState = (location?: { latitude: number; longitude: number }) => {
const timeoutRef = useRef(); // reference for initial timeout until first minute change
useEffect(() => {
const language = getLanguageByCode(locale ?? 'en');
- moment.locale(language.momentLocale);
+ dayjs.locale(language.locale);
setDate(getNewDate(timezone));
timeoutRef.current = setTimeout(
() => {
@@ -150,9 +155,8 @@ const useDateState = (location?: { latitude: number; longitude: number }) => {
}, 1000 * 60);
//1 minute - current seconds and milliseconds count
},
- 1000 * 60 - (1000 * moment().seconds() + moment().milliseconds())
+ 1000 * 60 - (1000 * dayjs().second() + dayjs().millisecond())
);
-
return () => timeoutRef.current && clearTimeout(timeoutRef.current);
}, [timezone, locale]);
@@ -162,9 +166,9 @@ const useDateState = (location?: { latitude: number; longitude: number }) => {
//Returns a local date if no inputs or returns date from input zone
const getNewDate = (timezone?: string) => {
if (timezone) {
- return moment().tz(timezone);
+ return dayjs().tz(timezone);
}
- return moment();
+ return dayjs();
};
export default definition;
diff --git a/src/widgets/iframe/IFrameTile.tsx b/src/widgets/iframe/IFrameTile.tsx
index 07bb951b3..86b32c89c 100644
--- a/src/widgets/iframe/IFrameTile.tsx
+++ b/src/widgets/iframe/IFrameTile.tsx
@@ -121,7 +121,7 @@ function IFrameTile({ widget }: IFrameTileProps) {
title="widget iframe"
allow={allowedPermissions.join(' ')}
>
- Your Browser does not support iframes. Please update your browser.
+ {t('card.errors.browserSupport')}
);
diff --git a/src/widgets/media-requests/MediaRequestListTile.tsx b/src/widgets/media-requests/MediaRequestListTile.tsx
index 512dbe45d..83eccc7a8 100644
--- a/src/widgets/media-requests/MediaRequestListTile.tsx
+++ b/src/widgets/media-requests/MediaRequestListTile.tsx
@@ -58,12 +58,13 @@ const useMediaRequestDecisionMutation = () => {
utils.mediaRequest.all.invalidate();
},
});
+ const { t } = useTranslation('modules/media-requests-list');
return async (variables: MediaRequestDecisionVariables) => {
- const action = variables.isApproved ? 'Approving' : 'Declining';
+ const action = variables.isApproved ? t('mutation.approving') : t('mutation.declining');
notifications.show({
id: `decide-${variables.request.id}`,
color: 'yellow',
- title: `${action} request...`,
+ title: `${action} ${t('mutation.request')}`,
message: undefined,
loading: true,
});
@@ -75,7 +76,7 @@ const useMediaRequestDecisionMutation = () => {
},
{
onSuccess(_data, variables) {
- const title = variables.isApproved ? 'Request was approved!' : 'Request was declined!';
+ const title = variables.isApproved ? t('mutation.approved') : t('mutation.declined');
notifications.update({
id: `decide-${variables.id}`,
color: 'teal',
@@ -187,7 +188,7 @@ function MediaRequestListTile({ widget }: MediaRequestListWidgetProps) {
notifications.show({
id: `approve ${item.id}`,
color: 'yellow',
- title: 'Approving request...',
+ title: t('tooltips.approving'),
message: undefined,
loading: true,
});
diff --git a/src/widgets/media-requests/MediaRequestStatsTile.tsx b/src/widgets/media-requests/MediaRequestStatsTile.tsx
index 2af61d3e3..a46bbe542 100644
--- a/src/widgets/media-requests/MediaRequestStatsTile.tsx
+++ b/src/widgets/media-requests/MediaRequestStatsTile.tsx
@@ -16,8 +16,8 @@ const definition = defineWidget({
type: 'select',
defaultValue: 'row' as 'row' | 'column',
data: [
- { label: 'Horizontal', value: 'row' },
- { label: 'Vertical', value: 'column' },
+ { value: 'row' },
+ { value: 'column' },
],
},
},
diff --git a/src/widgets/media-server/DetailCollapseable.tsx b/src/widgets/media-server/DetailCollapseable.tsx
index 6c0cea44d..85aa18531 100644
--- a/src/widgets/media-server/DetailCollapseable.tsx
+++ b/src/widgets/media-server/DetailCollapseable.tsx
@@ -2,27 +2,29 @@ import { Card, Divider, Flex, Grid, Group, Text } from '@mantine/core';
import { IconDeviceMobile, IconId } from '@tabler/icons-react';
import { GenericSessionInfo } from '../../types/api/media-server/session-info';
+import { useTranslation } from 'react-i18next';
export const DetailCollapseable = ({ session }: { session: GenericSessionInfo }) => {
let details: { title: string; metrics: { name: string; value: string | undefined }[] }[] = [];
+ const { t } = useTranslation('modules/media-server-list');
if (session.currentlyPlaying) {
if (session.currentlyPlaying.metadata.video) {
details = [
...details,
{
- title: 'Video',
+ title: t('detail.video.'),
metrics: [
{
- name: 'Resolution',
+ name: t('detail.video.resolution'),
value: `${session.currentlyPlaying.metadata.video.width}x${session.currentlyPlaying.metadata.video.height}`,
},
{
- name: 'Framerate',
+ name: t('detail.video.framerate'),
value: session.currentlyPlaying.metadata.video.videoFrameRate,
},
{
- name: 'Codec',
+ name: t('detail.video.codec'),
value: session.currentlyPlaying.metadata.video.videoCodec,
},
{
@@ -39,14 +41,14 @@ export const DetailCollapseable = ({ session }: { session: GenericSessionInfo })
details = [
...details,
{
- title: 'Audio',
+ title: t('detail.audio.audio'),
metrics: [
{
- name: 'Audio channels',
+ name: t('detail.audio.channels'),
value: `${session.currentlyPlaying.metadata.audio.audioChannels}`,
},
{
- name: 'Audio codec',
+ name: t('detail.audio.codec'),
value: session.currentlyPlaying.metadata.audio.audioCodec,
},
],
@@ -58,24 +60,24 @@ export const DetailCollapseable = ({ session }: { session: GenericSessionInfo })
details = [
...details,
{
- title: 'Transcoding',
+ title: t('detail.transcoding.transcoding'),
metrics: [
{
- name: 'Resolution',
+ name: t('detail.video.resolution'),
value: `${session.currentlyPlaying.metadata.transcoding.width}x${session.currentlyPlaying.metadata.transcoding.height}`,
},
{
- name: 'Context',
+ name: t('detail.transcoding.context'),
value: session.currentlyPlaying.metadata.transcoding.context,
},
{
- name: 'Hardware encoding requested',
+ name: t('detail.transcoding.requested'),
value: session.currentlyPlaying.metadata.transcoding.transcodeHwRequested
? 'yes'
: 'no',
},
{
- name: 'Source codec',
+ name: t('detail.transcoding.source'),
value:
session.currentlyPlaying.metadata.transcoding.sourceAudioCodec ||
session.currentlyPlaying.metadata.transcoding.sourceVideoCodec
@@ -83,7 +85,7 @@ export const DetailCollapseable = ({ session }: { session: GenericSessionInfo })
: undefined,
},
{
- name: 'Target codec',
+ name: t('detail.transcoding.target'),
value: `${session.currentlyPlaying.metadata.transcoding.videoCodec} ${session.currentlyPlaying.metadata.transcoding.audioCodec}`,
},
],
@@ -97,19 +99,19 @@ export const DetailCollapseable = ({ session }: { session: GenericSessionInfo })
- ID
+ {t('detail.id')}
{session.id}
- Device
+ {t('detail.device')}
{session.sessionName}
{details.length > 0 && (
-
+
)}
{details.map((detail, index) => (
diff --git a/src/widgets/media-server/MediaServerTile.tsx b/src/widgets/media-server/MediaServerTile.tsx
index c66d2f2e7..c2257ee49 100644
--- a/src/widgets/media-server/MediaServerTile.tsx
+++ b/src/widgets/media-server/MediaServerTile.tsx
@@ -42,7 +42,6 @@ interface MediaServerWidgetProps {
function MediaServerTile({ widget }: MediaServerWidgetProps) {
const { t } = useTranslation('modules/media-server');
const { config } = useConfigContext();
- const isEditMode = useEditModeStore((x) => x.enabled);
const { data, isError, isFetching, isInitialLoading } = useGetMediaServers({
enabled: config !== undefined,
@@ -72,7 +71,7 @@ function MediaServerTile({ widget }: MediaServerWidgetProps) {
{t('descriptor.name')}
- Homarr is loading streams...
+ {t('descriptor.loading')}
);
diff --git a/src/widgets/rss/RssWidgetTile.tsx b/src/widgets/rss/RssWidgetTile.tsx
index e9c9beec1..233b04dbd 100644
--- a/src/widgets/rss/RssWidgetTile.tsx
+++ b/src/widgets/rss/RssWidgetTile.tsx
@@ -42,6 +42,7 @@ const definition = defineWidget({
dangerousAllowSanitizedItemContent: {
type: 'switch',
defaultValue: false,
+ info: true,
},
textLinesClamp: {
type: 'slider',
diff --git a/src/widgets/torrent/TorrentQueueItem.tsx b/src/widgets/torrent/TorrentQueueItem.tsx
index 48d73a1ee..74600ca87 100644
--- a/src/widgets/torrent/TorrentQueueItem.tsx
+++ b/src/widgets/torrent/TorrentQueueItem.tsx
@@ -13,7 +13,7 @@ import {
createStyles,
useMantineTheme,
} from '@mantine/core';
-import { useDisclosure, useElementSize } from '@mantine/hooks';
+import { useDisclosure } from '@mantine/hooks';
import {
IconAffiliate,
IconDatabase,
@@ -37,9 +37,8 @@ interface TorrentQueueItemProps {
width: number;
}
-export const BitTorrrentQueueItem = ({ torrent, width, app }: TorrentQueueItemProps) => {
+export const BitTorrentQueueItem = ({ torrent, width, app }: TorrentQueueItemProps) => {
const [popoverOpened, { open: openPopover, close: closePopover }] = useDisclosure(false);
- const theme = useMantineTheme();
const { classes } = useStyles();
const { t } = useTranslation('modules/torrents-status');
diff --git a/src/widgets/torrent/TorrentTile.tsx b/src/widgets/torrent/TorrentTile.tsx
index d5fa11c9a..70df20529 100644
--- a/src/widgets/torrent/TorrentTile.tsx
+++ b/src/widgets/torrent/TorrentTile.tsx
@@ -25,7 +25,7 @@ import { AppIntegrationType } from '../../types/app';
import { useGetDownloadClientsQueue } from '../download-speed/useGetNetworkSpeed';
import { defineWidget } from '../helper';
import { IWidget } from '../widgets';
-import { BitTorrrentQueueItem } from './TorrentQueueItem';
+import { BitTorrentQueueItem } from './TorrentQueueItem';
dayjs.extend(duration);
dayjs.extend(relativeTime);
@@ -108,7 +108,7 @@ function TorrentTile({ widget }: TorrentTileProps) {
{t('card.loading.title')}
- Homarr is establishing a connection...
+ {t('card.loading.description')}
);
@@ -156,7 +156,7 @@ function TorrentTile({ widget }: TorrentTileProps) {
{filteredTorrents.map((torrent, index) => (
-
+
))}
{filteredTorrents.length !== torrents.length && (
diff --git a/src/widgets/useNet/UsenetQueueList.tsx b/src/widgets/useNet/UsenetQueueList.tsx
index f034d818b..125dd9df7 100644
--- a/src/widgets/useNet/UsenetQueueList.tsx
+++ b/src/widgets/useNet/UsenetQueueList.tsx
@@ -21,8 +21,8 @@ import duration from 'dayjs/plugin/duration';
import { useTranslation } from 'next-i18next';
import { FunctionComponent, useState } from 'react';
-import { useGetUsenetDownloads } from '../dashDot/api';
import { humanFileSize } from '../../tools/humanFileSize';
+import { useGetUsenetDownloads } from '../dashDot/api';
dayjs.extend(duration);
@@ -91,7 +91,6 @@ export const UsenetQueueList: FunctionComponent = ({ appId
- |
{t('queue.header.name')} |
{sizeBreakpoint < width ? (
{t('queue.header.size')} |
@@ -107,21 +106,6 @@ export const UsenetQueueList: FunctionComponent = ({ appId
{data.items.map((nzb) => (
- |
- {nzb.state === 'paused' ? (
-
-
-
-
-
- ) : (
-
-
-
-
-
- )}
- |
{
const { t } = useTranslation('modules/weather');
- const { width, ref } = useElementSize();
-
+
const { icon: Icon, name } =
weatherDefinitions.find((wd) => wd.codes.includes(code)) ?? unknownWeather;
diff --git a/src/widgets/weather/WeatherTile.tsx b/src/widgets/weather/WeatherTile.tsx
index a4443d7cd..8514bbcfc 100644
--- a/src/widgets/weather/WeatherTile.tsx
+++ b/src/widgets/weather/WeatherTile.tsx
@@ -4,7 +4,6 @@ import {
IconArrowDownRight,
IconArrowUpRight,
IconCloudRain,
- IconCurrentLocation,
IconMapPin,
} from '@tabler/icons-react';
import { api } from '~/utils/api';
@@ -12,6 +11,7 @@ import { api } from '~/utils/api';
import { defineWidget } from '../helper';
import { IWidget } from '../widgets';
import { WeatherIcon } from './WeatherIcon';
+import { useTranslation } from 'react-i18next';
const definition = defineWidget({
id: 'weather',
@@ -52,6 +52,7 @@ interface WeatherTileProps {
function WeatherTile({ widget }: WeatherTileProps) {
const { data: weather, isLoading, isError } = api.weather.at.useQuery(widget.properties.location);
const { width, ref } = useElementSize();
+ const { t } = useTranslation('modules/weather');
if (isLoading) {
return (
@@ -77,7 +78,7 @@ function WeatherTile({ widget }: WeatherTileProps) {
if (isError) {
return (
- An error occured
+ {t('error')}
);
}
diff --git a/yarn.lock b/yarn.lock
index ccf3a52cb..7902e41c6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5935,8 +5935,6 @@ __metadata:
i18next: ^22.5.1
immer: ^10.0.2
js-file-download: ^0.4.12
- moment: ^2.29.4
- moment-timezone: ^0.5.43
next: 13.4.10
next-i18next: ^13.0.0
node-mocks-http: ^1.12.2
@@ -7254,22 +7252,6 @@ __metadata:
languageName: node
linkType: hard
-"moment-timezone@npm:^0.5.43":
- version: 0.5.43
- resolution: "moment-timezone@npm:0.5.43"
- dependencies:
- moment: ^2.29.4
- checksum: 8075c897ed8a044f992ef26fe8cdbcad80caf974251db424cae157473cca03be2830de8c74d99341b76edae59f148c9d9d19c1c1d9363259085688ec1cf508d0
- languageName: node
- linkType: hard
-
-"moment@npm:^2.29.4":
- version: 2.29.4
- resolution: "moment@npm:2.29.4"
- checksum: 0ec3f9c2bcba38dc2451b1daed5daded747f17610b92427bebe1d08d48d8b7bdd8d9197500b072d14e326dd0ccf3e326b9e3d07c5895d3d49e39b6803b76e80e
- languageName: node
- linkType: hard
-
"mpd-parser@npm:^1.0.1, mpd-parser@npm:^1.1.1":
version: 1.1.1
resolution: "mpd-parser@npm:1.1.1"
|