fix: rtl common translation unnecessary (#1246)

* fix: rtl common translation unnecessary

* fix: format issue
This commit is contained in:
Meier Lukas
2024-10-05 16:31:15 +02:00
committed by GitHub
parent b14f82b4bb
commit 770768eb21
13 changed files with 40 additions and 95 deletions

View File

@@ -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>
); );
}; };

View File

@@ -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 />

View File

@@ -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>

View File

@@ -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)} />
)} )}

View File

@@ -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>

View File

@@ -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>
); );
}; };

View File

@@ -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={

View File

@@ -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: {

View File

@@ -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}

View File

@@ -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">

View File

@@ -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) => (

View File

@@ -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} />

View File

@@ -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>
); );
}; };