fix: rtl common translation unnecessary (#1246)
* fix: rtl common translation unnecessary * fix: format issue
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { MantineColorScheme, MantineColorSchemeManager } from "@mantine/core";
|
import type { MantineColorScheme, MantineColorSchemeManager } from "@mantine/core";
|
||||||
import { createTheme, isMantineColorScheme, MantineProvider } from "@mantine/core";
|
import { createTheme, DirectionProvider, isMantineColorScheme, MantineProvider } from "@mantine/core";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
@@ -14,16 +14,18 @@ export const CustomMantineProvider = ({ children }: PropsWithChildren) => {
|
|||||||
const manager = useColorSchemeManager();
|
const manager = useColorSchemeManager();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MantineProvider
|
<DirectionProvider>
|
||||||
defaultColorScheme="auto"
|
<MantineProvider
|
||||||
colorSchemeManager={manager}
|
defaultColorScheme="auto"
|
||||||
theme={createTheme({
|
colorSchemeManager={manager}
|
||||||
primaryColor: "red",
|
theme={createTheme({
|
||||||
autoContrast: true,
|
primaryColor: "red",
|
||||||
})}
|
autoContrast: true,
|
||||||
>
|
})}
|
||||||
{children}
|
>
|
||||||
</MantineProvider>
|
{children}
|
||||||
|
</MantineProvider>
|
||||||
|
</DirectionProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { env } from "@homarr/auth/env.mjs";
|
|||||||
import { auth } from "@homarr/auth/next";
|
import { auth } from "@homarr/auth/next";
|
||||||
import { ModalProvider } from "@homarr/modals";
|
import { ModalProvider } from "@homarr/modals";
|
||||||
import { Notifications } from "@homarr/notifications";
|
import { Notifications } from "@homarr/notifications";
|
||||||
|
import { getScopedI18n } from "@homarr/translation/server";
|
||||||
|
|
||||||
import { Analytics } from "~/components/layout/analytics";
|
import { Analytics } from "~/components/layout/analytics";
|
||||||
import { SearchEngineOptimization } from "~/components/layout/search-engine-optimization";
|
import { SearchEngineOptimization } from "~/components/layout/search-engine-optimization";
|
||||||
@@ -56,6 +57,8 @@ export const viewport: Viewport = {
|
|||||||
export default async function Layout(props: { children: React.ReactNode; params: { locale: string } }) {
|
export default async function Layout(props: { children: React.ReactNode; params: { locale: string } }) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
const colorScheme = cookies().get("homarr-color-scheme")?.value ?? "light";
|
const colorScheme = cookies().get("homarr-color-scheme")?.value ?? "light";
|
||||||
|
const tCommon = await getScopedI18n("common");
|
||||||
|
const direction = tCommon("direction");
|
||||||
|
|
||||||
const StackedProvider = composeWrappers([
|
const StackedProvider = composeWrappers([
|
||||||
(innerProps) => {
|
(innerProps) => {
|
||||||
@@ -70,7 +73,7 @@ export default async function Layout(props: { children: React.ReactNode; params:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
// Instead of ColorSchemScript we use data-mantine-color-scheme to prevent flickering
|
// Instead of ColorSchemScript we use data-mantine-color-scheme to prevent flickering
|
||||||
<html lang="en" data-mantine-color-scheme={colorScheme} suppressHydrationWarning>
|
<html lang="en" dir={direction} data-mantine-color-scheme={colorScheme} suppressHydrationWarning>
|
||||||
<head>
|
<head>
|
||||||
<Analytics />
|
<Analytics />
|
||||||
<SearchEngineOptimization />
|
<SearchEngineOptimization />
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export default async function SearchEnginesPage(props: SearchEnginesPageProps) {
|
|||||||
const searchParams = searchParamsSchema.parse(props.searchParams);
|
const searchParams = searchParamsSchema.parse(props.searchParams);
|
||||||
const { items: searchEngines, totalCount } = await api.searchEngine.getPaginated(searchParams);
|
const { items: searchEngines, totalCount } = await api.searchEngine.getPaginated(searchParams);
|
||||||
|
|
||||||
const t = await getI18n();
|
|
||||||
const tEngine = await getScopedI18n("search.engine");
|
const tEngine = await getScopedI18n("search.engine");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -40,13 +39,7 @@ export default async function SearchEnginesPage(props: SearchEnginesPageProps) {
|
|||||||
<Stack>
|
<Stack>
|
||||||
<Title>{tEngine("page.list.title")}</Title>
|
<Title>{tEngine("page.list.title")}</Title>
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<SearchInput
|
<SearchInput placeholder={`${tEngine("search")}...`} defaultValue={searchParams.search} />
|
||||||
placeholder={t("common.rtl", {
|
|
||||||
value: tEngine("search"),
|
|
||||||
symbol: "...",
|
|
||||||
})}
|
|
||||||
defaultValue={searchParams.search}
|
|
||||||
/>
|
|
||||||
<MobileAffixButton component={Link} href="/manage/search-engines/new">
|
<MobileAffixButton component={Link} href="/manage/search-engines/new">
|
||||||
{tEngine("page.create.title")}
|
{tEngine("page.create.title")}
|
||||||
</MobileAffixButton>
|
</MobileAffixButton>
|
||||||
|
|||||||
@@ -48,13 +48,7 @@ export default async function GroupsDetailPage({ params, searchParams }: GroupsD
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<SearchInput
|
<SearchInput placeholder={`${tMembers("search")}...`} defaultValue={searchParams.search} />
|
||||||
placeholder={t("common.rtl", {
|
|
||||||
value: tMembers("search"),
|
|
||||||
symbol: "...",
|
|
||||||
})}
|
|
||||||
defaultValue={searchParams.search}
|
|
||||||
/>
|
|
||||||
{isProviderEnabled("credentials") && (
|
{isProviderEnabled("credentials") && (
|
||||||
<AddGroupMember groupId={group.id} presentUserIds={group.members.map((member) => member.id)} />
|
<AddGroupMember groupId={group.id} presentUserIds={group.members.map((member) => member.id)} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -36,13 +36,7 @@ export default async function GroupsListPage(props: GroupsListPageProps) {
|
|||||||
<Stack>
|
<Stack>
|
||||||
<Title>{t("group.title")}</Title>
|
<Title>{t("group.title")}</Title>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<SearchInput
|
<SearchInput placeholder={`${t("group.search")}...`} defaultValue={searchParams.search} />
|
||||||
placeholder={t("common.rtl", {
|
|
||||||
value: t("group.search"),
|
|
||||||
symbol: "...",
|
|
||||||
})}
|
|
||||||
defaultValue={searchParams.search}
|
|
||||||
/>
|
|
||||||
<AddGroup />
|
<AddGroup />
|
||||||
</Group>
|
</Group>
|
||||||
<Table striped highlightOnHover>
|
<Table striped highlightOnHover>
|
||||||
|
|||||||
@@ -21,10 +21,7 @@ export const DesktopSearchInput = () => {
|
|||||||
leftSection={<IconSearch size={20} stroke={1.5} />}
|
leftSection={<IconSearch size={20} stroke={1.5} />}
|
||||||
onClick={openSpotlight}
|
onClick={openSpotlight}
|
||||||
>
|
>
|
||||||
{t("common.rtl", {
|
{`${t("search.placeholder")}...`}
|
||||||
value: t("search.placeholder"),
|
|
||||||
symbol: "...",
|
|
||||||
})}
|
|
||||||
</TextInput>
|
</TextInput>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,10 +51,7 @@ export const Spotlight = () => {
|
|||||||
store={spotlightStore}
|
store={spotlightStore}
|
||||||
>
|
>
|
||||||
<MantineSpotlight.Search
|
<MantineSpotlight.Search
|
||||||
placeholder={t("common.rtl", {
|
placeholder={`${t("search.placeholder")}...`}
|
||||||
value: t("search.placeholder"),
|
|
||||||
symbol: "...",
|
|
||||||
})}
|
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
leftSectionWidth={activeMode.modeKey !== "help" ? 80 : 48}
|
leftSectionWidth={activeMode.modeKey !== "help" ? 80 : 48}
|
||||||
leftSection={
|
leftSection={
|
||||||
|
|||||||
@@ -527,10 +527,8 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
common: {
|
common: {
|
||||||
rtl: "{value}{symbol}",
|
// Either "ltr" or "rtl"
|
||||||
symbols: {
|
direction: "ltr",
|
||||||
colon: ": ",
|
|
||||||
},
|
|
||||||
beta: "Beta",
|
beta: "Beta",
|
||||||
error: "Error",
|
error: "Error",
|
||||||
action: {
|
action: {
|
||||||
|
|||||||
@@ -59,11 +59,7 @@ export default function AppWidget({ options, isEditMode }: WidgetComponentProps<
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Tooltip.Floating>
|
</Tooltip.Floating>
|
||||||
{options.pingEnabled && app.href ? (
|
{options.pingEnabled && app.href ? (
|
||||||
<Suspense
|
<Suspense fallback={<PingDot color="blue" tooltip={`${t("common.action.loading")}…`} />}>
|
||||||
fallback={
|
|
||||||
<PingDot color="blue" tooltip={t("common.rtl", { symbol: "…", value: t("common.action.loading") })} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<PingIndicator href={app.href} />
|
<PingIndicator href={app.href} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -100,14 +100,11 @@ const stats = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: IconPercentage,
|
icon: IconPercentage,
|
||||||
value: (data, t) =>
|
value: (data) =>
|
||||||
t("common.rtl", {
|
`${formatNumber(
|
||||||
value: formatNumber(
|
data.reduce((count, { adsBlockedTodayPercentage }) => count + adsBlockedTodayPercentage, 0),
|
||||||
data.reduce((count, { adsBlockedTodayPercentage }) => count + adsBlockedTodayPercentage, 0),
|
2,
|
||||||
2,
|
)}%`,
|
||||||
),
|
|
||||||
symbol: "%",
|
|
||||||
}),
|
|
||||||
label: (t) => t("widget.dnsHoleSummary.data.adsBlockedTodayPercentage"),
|
label: (t) => t("widget.dnsHoleSummary.data.adsBlockedTodayPercentage"),
|
||||||
color: "rgba(255, 165, 20, 0.4)", // YELLOW
|
color: "rgba(255, 165, 20, 0.4)", // YELLOW
|
||||||
},
|
},
|
||||||
@@ -135,7 +132,7 @@ const stats = [
|
|||||||
|
|
||||||
interface StatItem {
|
interface StatItem {
|
||||||
icon: TablerIcon;
|
icon: TablerIcon;
|
||||||
value: (x: DnsHoleSummary[], t: TranslationFunction) => string;
|
value: (x: DnsHoleSummary[]) => string;
|
||||||
label: stringOrTranslation;
|
label: stringOrTranslation;
|
||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
@@ -184,14 +181,14 @@ const StatCard = ({ item, data, usePiHoleColors, t }: StatCardProps) => {
|
|||||||
gap="1cqmin"
|
gap="1cqmin"
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
key={item.value(data, t)}
|
key={item.value(data)}
|
||||||
className="summary-card-value text-flash"
|
className="summary-card-value text-flash"
|
||||||
ta="center"
|
ta="center"
|
||||||
size="20cqmin"
|
size="20cqmin"
|
||||||
fw="bold"
|
fw="bold"
|
||||||
style={{ "--glow-size": "2.5cqmin" }}
|
style={{ "--glow-size": "2.5cqmin" }}
|
||||||
>
|
>
|
||||||
{item.value(data, t)}
|
{item.value(data)}
|
||||||
</Text>
|
</Text>
|
||||||
{item.label && (
|
{item.label && (
|
||||||
<Text className="summary-card-label" ta="center" size="15cqmin">
|
<Text className="summary-card-label" ta="center" size="15cqmin">
|
||||||
|
|||||||
@@ -641,8 +641,6 @@ export default function DownloadClientsWidget({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLangRtl = tCommon("rtl", { value: "0", symbol: "1" }).startsWith("1");
|
|
||||||
|
|
||||||
//Used for Global Torrent Ratio
|
//Used for Global Torrent Ratio
|
||||||
const globalTraffic = clients
|
const globalTraffic = clients
|
||||||
.filter(({ integration: { kind } }) =>
|
.filter(({ integration: { kind } }) =>
|
||||||
@@ -676,13 +674,12 @@ export default function DownloadClientsWidget({
|
|||||||
px="var(--space-size)"
|
px="var(--space-size)"
|
||||||
justify={integrationTypes.includes("torrent") ? "space-between" : "end"}
|
justify={integrationTypes.includes("torrent") ? "space-between" : "end"}
|
||||||
style={{
|
style={{
|
||||||
flexDirection: isLangRtl ? "row-reverse" : "row",
|
|
||||||
borderTop: "0.0625rem solid var(--border-color)",
|
borderTop: "0.0625rem solid var(--border-color)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{integrationTypes.includes("torrent") && (
|
{integrationTypes.includes("torrent") && (
|
||||||
<Group pt="var(--space-size)" style={{ flexDirection: isLangRtl ? "row-reverse" : "row" }}>
|
<Group pt="var(--space-size)">
|
||||||
<Text>{tCommon("rtl", { value: t("globalRatio"), symbol: tCommon("symbols.colon") })}</Text>
|
<Text>{`${t("globalRatio")}:`}</Text>
|
||||||
<Text>{(globalTraffic.up / globalTraffic.down).toFixed(2)}</Text>
|
<Text>{(globalTraffic.up / globalTraffic.down).toFixed(2)}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
@@ -758,21 +755,10 @@ const NormalizedLine = ({
|
|||||||
values?: number | string | string[];
|
values?: number | string | string[];
|
||||||
}) => {
|
}) => {
|
||||||
const t = useScopedI18n("widget.downloads.items");
|
const t = useScopedI18n("widget.downloads.items");
|
||||||
const tCommon = useScopedI18n("common");
|
|
||||||
const translatedKey = t(`${itemKey}.detailsTitle`);
|
|
||||||
const isLangRtl = tCommon("rtl", { value: "0", symbol: "1" }).startsWith("1"); //Maybe make a common "isLangRtl" somewhere
|
|
||||||
const keyString = tCommon("rtl", { value: translatedKey, symbol: tCommon("symbols.colon") });
|
|
||||||
if (typeof values !== "number" && (values === undefined || values.length === 0)) return null;
|
if (typeof values !== "number" && (values === undefined || values.length === 0)) return null;
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group w="100%" display="flex" align="top" justify="space-between" wrap="nowrap">
|
||||||
w="100%"
|
<Text>{`${t(`${itemKey}.detailsTitle`)}:`}</Text>
|
||||||
display="flex"
|
|
||||||
style={{ flexDirection: isLangRtl ? "row-reverse" : "row" }}
|
|
||||||
align="top"
|
|
||||||
justify="space-between"
|
|
||||||
wrap="nowrap"
|
|
||||||
>
|
|
||||||
<Text>{keyString}</Text>
|
|
||||||
{Array.isArray(values) ? (
|
{Array.isArray(values) ? (
|
||||||
<Stack>
|
<Stack>
|
||||||
{values.map((value) => (
|
{values.map((value) => (
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export default function MediaServerWidget({
|
|||||||
itemId,
|
itemId,
|
||||||
}: WidgetComponentProps<"mediaRequests-requestStats">) {
|
}: WidgetComponentProps<"mediaRequests-requestStats">) {
|
||||||
const t = useScopedI18n("widget.mediaRequests-requestStats");
|
const t = useScopedI18n("widget.mediaRequests-requestStats");
|
||||||
const tCommon = useScopedI18n("common");
|
|
||||||
const isQueryEnabled = Boolean(itemId);
|
const isQueryEnabled = Boolean(itemId);
|
||||||
const { data: requestStats, isError: _isError } = clientApi.widget.mediaRequests.getStats.useQuery(
|
const { data: requestStats, isError: _isError } = clientApi.widget.mediaRequests.getStats.useQuery(
|
||||||
{
|
{
|
||||||
@@ -188,8 +187,7 @@ export default function MediaServerWidget({
|
|||||||
{user.displayName}
|
{user.displayName}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className="mediaRequests-stats-users-user-request-count" size="4cqmin">
|
<Text className="mediaRequests-stats-users-user-request-count" size="4cqmin">
|
||||||
{tCommon("rtl", { value: t("titles.users.requests"), symbol: tCommon("symbols.colon") }) +
|
{`${t("titles.users.requests")}: ${user.requestCount}`}
|
||||||
user.requestCount}
|
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Space flex={1} />
|
<Space flex={1} />
|
||||||
|
|||||||
@@ -62,18 +62,8 @@ export const WeatherDescription = ({ weatherOnly, time, weatherCode, maxTemp, mi
|
|||||||
<Stack align="center" gap="0">
|
<Stack align="center" gap="0">
|
||||||
<Text fz="24px">{dayjs(time).format("dddd MMMM D YYYY")}</Text>
|
<Text fz="24px">{dayjs(time).format("dddd MMMM D YYYY")}</Text>
|
||||||
<Text fz="16px">{t(`kind.${name}`)}</Text>
|
<Text fz="16px">{t(`kind.${name}`)}</Text>
|
||||||
<Text fz="16px">
|
<Text fz="16px">{`${tCommon("information.max")}: ${maxTemp}`}</Text>
|
||||||
{tCommon("rtl", {
|
<Text fz="16px">{`${tCommon("information.min")}: ${minTemp}`}</Text>
|
||||||
value: tCommon("information.max"),
|
|
||||||
symbol: tCommon("symbols.colon"),
|
|
||||||
}) + maxTemp}
|
|
||||||
</Text>
|
|
||||||
<Text fz="16px">
|
|
||||||
{tCommon("rtl", {
|
|
||||||
value: tCommon("information.min"),
|
|
||||||
symbol: tCommon("symbols.colon"),
|
|
||||||
}) + minTemp}
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user