Internationalization (i18n) (#627)
* `feat: added internationalization lib, added english labels` * `fixes from code review` * `feat: add crowdin github actions` * `fix: added support for i18n in testing. Fixed broken tests` * `fix: missing translations` * `removed unneded import` * `more components updated with missing translations` * `fixed lint issue` * `Refactor: updated how translations are scoped, updated all references` * `fixing broken tests` * `feat: added language switcher, updated some translations` * `fixed linting issues` * `reverting vite config` * `add english group id's to command palette` * `updated PR template for i18n`
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
} from "@core/stores/messageStore/index.ts";
|
||||
import { Protobuf, Types } from "@meshtastic/js";
|
||||
import { Message } from "@core/stores/messageStore/types.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
// import { MessageActionsMenu } from "@components/PageComponents/Messages/MessageActionsMenu.tsx"; // Uncomment if needed later
|
||||
|
||||
interface MessageStatusInfo {
|
||||
@@ -26,37 +27,6 @@ interface MessageStatusInfo {
|
||||
iconClassName?: string;
|
||||
}
|
||||
|
||||
const MESSAGE_STATUS_MAP: Record<MessageState, MessageStatusInfo> = {
|
||||
[MessageState.Ack]: {
|
||||
displayText: "Message delivered",
|
||||
icon: CheckCircle2,
|
||||
ariaLabel: "Message delivered",
|
||||
iconClassName: "text-green-500",
|
||||
},
|
||||
[MessageState.Waiting]: {
|
||||
displayText: "Waiting for delivery",
|
||||
icon: CircleEllipsis,
|
||||
ariaLabel: "Sending message",
|
||||
iconClassName: "text-slate-400",
|
||||
},
|
||||
[MessageState.Failed]: {
|
||||
displayText: "Delivery failed",
|
||||
icon: AlertCircle,
|
||||
ariaLabel: "Message delivery failed",
|
||||
iconClassName: "text-red-500 dark:text-red-400",
|
||||
},
|
||||
};
|
||||
|
||||
const UNKNOWN_STATUS: MessageStatusInfo = {
|
||||
displayText: "Unknown state",
|
||||
icon: AlertCircle,
|
||||
ariaLabel: "Message status unknown",
|
||||
iconClassName: "text-red-500 dark:text-red-400",
|
||||
};
|
||||
|
||||
const getMessageStatusInfo = (state: MessageState): MessageStatusInfo =>
|
||||
MESSAGE_STATUS_MAP[state] ?? UNKNOWN_STATUS;
|
||||
|
||||
const StatusTooltip = (
|
||||
{ statusInfo, children }: {
|
||||
statusInfo: MessageStatusInfo;
|
||||
@@ -81,16 +51,55 @@ interface MessageItemProps {
|
||||
export const MessageItem = ({ message }: MessageItemProps) => {
|
||||
const { getNode } = useDevice();
|
||||
const { getMyNodeNum } = useMessageStore();
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
const MESSAGE_STATUS_MAP = useMemo(
|
||||
(): Record<MessageState, MessageStatusInfo> => ({
|
||||
[MessageState.Ack]: {
|
||||
displayText: t("message_item_status_delivered_displayText"),
|
||||
icon: CheckCircle2,
|
||||
ariaLabel: t("message_item_status_delivered_ariaLabel"),
|
||||
iconClassName: "text-green-500",
|
||||
},
|
||||
[MessageState.Waiting]: {
|
||||
displayText: t("message_item_status_waiting_displayText"),
|
||||
icon: CircleEllipsis,
|
||||
ariaLabel: t("message_item_status_waiting_ariaLabel"),
|
||||
iconClassName: "text-slate-400",
|
||||
},
|
||||
[MessageState.Failed]: {
|
||||
displayText: t("message_item_status_failed_displayText"),
|
||||
icon: AlertCircle,
|
||||
ariaLabel: t("message_item_status_failed_ariaLabel"),
|
||||
iconClassName: "text-red-500 dark:text-red-400",
|
||||
},
|
||||
}),
|
||||
[t],
|
||||
);
|
||||
|
||||
const UNKNOWN_STATUS = useMemo((): MessageStatusInfo => ({
|
||||
displayText: t("message_item_status_unknown_displayText"),
|
||||
icon: AlertCircle,
|
||||
ariaLabel: t("message_item_status_unknown_ariaLabel"),
|
||||
iconClassName: "text-red-500 dark:text-red-400",
|
||||
}), [t]);
|
||||
|
||||
const getMessageStatusInfo = useMemo(
|
||||
() => (state: MessageState): MessageStatusInfo =>
|
||||
MESSAGE_STATUS_MAP[state] ?? UNKNOWN_STATUS,
|
||||
[MESSAGE_STATUS_MAP, UNKNOWN_STATUS],
|
||||
);
|
||||
|
||||
const messageUser: Protobuf.Mesh.NodeInfo | null | undefined = useMemo(() => {
|
||||
return message.from != null ? getNode(message.from) : null;
|
||||
}, [getNode, message.from]);
|
||||
|
||||
const myNodeNum = useMemo(() => getMyNodeNum(), [getMyNodeNum]);
|
||||
|
||||
const { displayName, shortName, isFavorite } = useMemo(() => {
|
||||
const userIdHex = message.from.toString(16).toUpperCase().padStart(2, "0");
|
||||
const last4 = userIdHex.slice(-4);
|
||||
const fallbackName = `Meshtastic ${last4}`;
|
||||
const fallbackName = t("message_item_fallbackName_withLastFour", { last4 });
|
||||
const longName = messageUser?.user?.longName;
|
||||
const derivedShortName = messageUser?.user?.shortName || fallbackName;
|
||||
const derivedDisplayName = longName || derivedShortName;
|
||||
@@ -101,7 +110,7 @@ export const MessageItem = ({ message }: MessageItemProps) => {
|
||||
shortName: derivedShortName,
|
||||
isFavorite: isFavorite,
|
||||
};
|
||||
}, [messageUser, message.from]);
|
||||
}, [messageUser, message.from, t, myNodeNum]);
|
||||
|
||||
const messageStatusInfo = getMessageStatusInfo(message.state);
|
||||
const StatusIconComponent = messageStatusInfo.icon;
|
||||
@@ -110,7 +119,7 @@ export const MessageItem = ({ message }: MessageItemProps) => {
|
||||
() => message.date ? new Date(message.date) : null,
|
||||
[message.date],
|
||||
);
|
||||
const locale = "en-US"; // TODO: Make dynamic via props or context
|
||||
const locale = i18n.language;
|
||||
|
||||
const formattedTime = useMemo(
|
||||
() =>
|
||||
|
||||
Reference in New Issue
Block a user