From 1a55855ce91aa6467421e7457d1b55752416c1ef Mon Sep 17 00:00:00 2001 From: Donkie Date: Sat, 23 Nov 2024 23:16:41 +0100 Subject: [PATCH] Some i18n stuff --- client/public/locales/en/common.json | 7 +++++ client/public/locales/sv/common.json | 9 ++++++- client/src/i18n.ts | 27 +++++++++++++++++++ .../components/locationContainer.tsx | 7 ++--- .../pages/locations/components/spoolCard.tsx | 23 +++++++++++++++- 5 files changed, 68 insertions(+), 5 deletions(-) diff --git a/client/public/locales/en/common.json b/client/public/locales/en/common.json index 45df776..c1459e9 100644 --- a/client/public/locales/en/common.json +++ b/client/public/locales/en/common.json @@ -208,6 +208,9 @@ }, "messages": { "archive": "Are you sure you want to archive this spool?" + }, + "formats": { + "last_used": "Last used {{date}}" } }, "filament": { @@ -377,5 +380,9 @@ "create": "Create Manufacturer | Spoolman", "clone": "#{{id}} Clone Manufacturer | Spoolman" } + }, + "locations": { + "new_location": "New Location", + "no_location": "No Location" } } diff --git a/client/public/locales/sv/common.json b/client/public/locales/sv/common.json index e36c408..39977fc 100644 --- a/client/public/locales/sv/common.json +++ b/client/public/locales/sv/common.json @@ -210,6 +210,9 @@ "measurement_type_label": "Mättyp", "adjust_filament_value": "Mängd att konsumera", "adjust_filament_help": "Här kan du direkt lägga till eller subtrahera filament från spolen. Ett positivt värde kommer att konsumera filament, ett negativt värde kommer att lägga till det." + }, + "formats": { + "last_used": "Senast använd {{date}}" } }, "filament": { @@ -376,5 +379,9 @@ "description": "Hjälp

Här är några tips för att komma igång.

Spoolman innehåller 3 olika typer av data:

För att lägga till en ny spole i databasen måste du först skapa ett Filament-objekt för den. När det är klart kan du sedan skapa ett Spol-objekt för den individuella spolen. Om du sedan köper ytterligare spolar av samma filament kan du bara skapa ytterligare Spol-objekt och återanvända samma Filament-objekt.

Du kan valfritt också skapa ett Tillverkar-objekt för företaget som tillverkar filamentet om du vill spåra den informationen.

Du kan koppla andra 3D-skrivartjänster till Spoolman, som Moonraker, som sedan automatiskt kan spåra filamentanvändning och uppdatera Spol-objekten åt dig. Se Spoolman README för hur du gör det.

", "help": "Hjälp" }, - "kofi": "Dricksa mig på Ko-fi" + "kofi": "Dricksa mig på Ko-fi", + "locations": { + "new_location": "Ny plats", + "no_location": "Ingen plats" + } } diff --git a/client/src/i18n.ts b/client/src/i18n.ts index a83382e..ac1f79b 100644 --- a/client/src/i18n.ts +++ b/client/src/i18n.ts @@ -1,3 +1,4 @@ +import dayjs from "dayjs"; import i18n from "i18next"; import detector from "i18next-browser-languagedetector"; import Backend from "i18next-http-backend"; @@ -8,6 +9,7 @@ interface Language { name: string; countryCode: string; fullCode: string; + djs: () => Promise; } /** @@ -16,107 +18,128 @@ interface Language { * name: Name of the language in the list * countryCode: Country code of the country's flag to display for this language * fullCode: Full language code, used for Ant Design's locale + * djs: Function to load the dayjs locale, see https://github.com/iamkun/dayjs/tree/dev/src/locale for list of locales */ export const languages: { [key: string]: Language } = { ["en"]: { name: "English", countryCode: "gb", fullCode: "en-GB", + djs: () => import("dayjs/locale/en"), }, ["sv"]: { name: "Svenska", countryCode: "se", fullCode: "sv-SE", + djs: () => import("dayjs/locale/sv"), }, ["de"]: { name: "Deutsch", countryCode: "de", fullCode: "de-DE", + djs: () => import("dayjs/locale/de"), }, ["es"]: { name: "Español", countryCode: "es", fullCode: "es-ES", + djs: () => import("dayjs/locale/es"), }, ["zh"]: { name: "简体中文", countryCode: "cn", fullCode: "zh-CN", + djs: () => import("dayjs/locale/zh-cn"), }, ["zh-Hant"]: { name: "繁體中文", countryCode: "cn", fullCode: "zh-TW", + djs: () => import("dayjs/locale/zh-hk"), }, ["pl"]: { name: "Polski", countryCode: "pl", fullCode: "pl-PL", + djs: () => import("dayjs/locale/pl"), }, ["ru"]: { name: "Русский", countryCode: "ru", fullCode: "ru-RU", + djs: () => import("dayjs/locale/ru"), }, ["cs"]: { name: "Česky", countryCode: "cz", fullCode: "cs-CZ", + djs: () => import("dayjs/locale/cs"), }, ["nb-NO"]: { name: "Norsk bokmål", countryCode: "no", fullCode: "nb-NO", + djs: () => import("dayjs/locale/nb"), }, ["nl"]: { name: "Nederlands", countryCode: "nl", fullCode: "nl-NL", + djs: () => import("dayjs/locale/nl"), }, ["fr"]: { name: "Français", countryCode: "fr", fullCode: "fr-FR", + djs: () => import("dayjs/locale/fr"), }, ["hu"]: { name: "Magyar", countryCode: "hu", fullCode: "hu-HU", + djs: () => import("dayjs/locale/hu"), }, ["it"]: { name: "Italiano", countryCode: "it", fullCode: "it-IT", + djs: () => import("dayjs/locale/it"), }, ["uk"]: { name: "Українська", countryCode: "ua", fullCode: "uk-UA", + djs: () => import("dayjs/locale/uk"), }, ["el"]: { name: "Ελληνικά", countryCode: "gr", fullCode: "el-GR", + djs: () => import("dayjs/locale/el"), }, ["da"]: { name: "Dansk", countryCode: "dk", fullCode: "da-DK", + djs: () => import("dayjs/locale/da"), }, ["pt"]: { name: "Português", countryCode: "pt", fullCode: "pt-PT", + djs: () => import("dayjs/locale/pt"), }, ["fa"]: { name: "فارسی", countryCode: "ir", fullCode: "fa-IR", + djs: () => import("dayjs/locale/fa"), }, ["ro"]: { name: "Român", countryCode: "ro", fullCode: "ro-RO", + djs: () => import("dayjs/locale/ro"), }, }; @@ -134,4 +157,8 @@ i18n fallbackLng: "en", }); +i18n.on("languageChanged", function (lng) { + languages[lng].djs().then((djs) => dayjs.locale(djs.name)); +}); + export default i18n; diff --git a/client/src/pages/locations/components/locationContainer.tsx b/client/src/pages/locations/components/locationContainer.tsx index 1cac77b..cecb607 100644 --- a/client/src/pages/locations/components/locationContainer.tsx +++ b/client/src/pages/locations/components/locationContainer.tsx @@ -41,7 +41,7 @@ export function LocationContainer() { const grouped: Record = {}; spools.forEach((spool) => { - const loc = spool.location ?? t("spool.no_location"); + const loc = spool.location ?? t("locations.no_location"); if (!grouped[loc]) { grouped[loc] = []; } @@ -155,12 +155,13 @@ export function LocationContainer() { } const addNewLocation = () => { - let newLocationName = "New Location"; + const baseLocationName = t("locations.new_location"); + let newLocationName = baseLocationName; const newLocs = [...locationsList]; let i = 1; while (newLocs.includes(newLocationName)) { - newLocationName = "New Location " + i; + newLocationName = baseLocationName + " " + i; i++; } newLocs.push(newLocationName); diff --git a/client/src/pages/locations/components/spoolCard.tsx b/client/src/pages/locations/components/spoolCard.tsx index 6da9a60..0f2c7f1 100644 --- a/client/src/pages/locations/components/spoolCard.tsx +++ b/client/src/pages/locations/components/spoolCard.tsx @@ -4,12 +4,18 @@ import type { Identifier, XYCoord } from "dnd-core"; import { useDrag, useDrop } from "react-dnd"; import { EditOutlined, EyeOutlined } from "@ant-design/icons"; +import dayjs from "dayjs"; +import relativeTime from "dayjs/plugin/relativeTime"; +import utc from "dayjs/plugin/utc"; import { useEffect, useRef } from "react"; import { Link } from "react-router-dom"; import SpoolIcon from "../../../components/spoolIcon"; import { ISpool } from "../../spools/model"; import { ItemTypes, SpoolDragItem, useCurrentDraggedSpool } from "../dnd"; +dayjs.extend(utc); +dayjs.extend(relativeTime); + const { useToken } = theme; export function SpoolCard({ @@ -134,6 +140,21 @@ export function SpoolCard({ }; drag(drop(ref)); + function formatSubtitle(spool: ISpool) { + let str = ""; + if (spool.filament.material) str += spool.filament.material + " - "; + if (spool.filament.weight) { + const remaining_weight = spool.remaining_weight ?? spool.filament.weight; + str += `${remaining_weight} / ${spool.filament.weight} g`; + } + if (spool.last_used) { + // Format like "last used X time ago" + const dt = dayjs(spool.last_used); + str += ` - ${t("spool.formats.last_used", { date: dt.fromNow() })}`; + } + return str; + } + return (
@@ -157,7 +178,7 @@ export function SpoolCard({ color: token.colorTextSecondary, }} > - {spool.remaining_weight} / {spool.filament.weight} g + {formatSubtitle(spool)}