Some i18n stuff

This commit is contained in:
Donkie
2024-11-23 23:16:41 +01:00
parent 67c5b34bf5
commit 1a55855ce9
5 changed files with 68 additions and 5 deletions

View File

@@ -208,6 +208,9 @@
}, },
"messages": { "messages": {
"archive": "Are you sure you want to archive this spool?" "archive": "Are you sure you want to archive this spool?"
},
"formats": {
"last_used": "Last used {{date}}"
} }
}, },
"filament": { "filament": {
@@ -377,5 +380,9 @@
"create": "Create Manufacturer | Spoolman", "create": "Create Manufacturer | Spoolman",
"clone": "#{{id}} Clone Manufacturer | Spoolman" "clone": "#{{id}} Clone Manufacturer | Spoolman"
} }
},
"locations": {
"new_location": "New Location",
"no_location": "No Location"
} }
} }

View File

@@ -210,6 +210,9 @@
"measurement_type_label": "Mättyp", "measurement_type_label": "Mättyp",
"adjust_filament_value": "Mängd att konsumera", "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." "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": { "filament": {
@@ -376,5 +379,9 @@
"description": "<title>Hjälp</title><p>Här är några tips för att komma igång.</p><p>Spoolman innehåller 3 olika typer av data:</p><itemsHelp/><p>För att lägga till en ny spole i databasen måste du först skapa ett <filamentCreateLink>Filament</filamentCreateLink>-objekt för den. När det är klart kan du sedan skapa ett <spoolCreateLink>Spol</spoolCreateLink>-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.</p><p>Du kan valfritt också skapa ett <vendorCreateLink>Tillverkar</vendorCreateLink>-objekt för företaget som tillverkar filamentet om du vill spåra den informationen.</p><p>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 <readmeLink>Spoolman README</readmeLink> för hur du gör det.</p>", "description": "<title>Hjälp</title><p>Här är några tips för att komma igång.</p><p>Spoolman innehåller 3 olika typer av data:</p><itemsHelp/><p>För att lägga till en ny spole i databasen måste du först skapa ett <filamentCreateLink>Filament</filamentCreateLink>-objekt för den. När det är klart kan du sedan skapa ett <spoolCreateLink>Spol</spoolCreateLink>-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.</p><p>Du kan valfritt också skapa ett <vendorCreateLink>Tillverkar</vendorCreateLink>-objekt för företaget som tillverkar filamentet om du vill spåra den informationen.</p><p>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 <readmeLink>Spoolman README</readmeLink> för hur du gör det.</p>",
"help": "Hjälp" "help": "Hjälp"
}, },
"kofi": "Dricksa mig på Ko-fi" "kofi": "Dricksa mig på Ko-fi",
"locations": {
"new_location": "Ny plats",
"no_location": "Ingen plats"
}
} }

View File

@@ -1,3 +1,4 @@
import dayjs from "dayjs";
import i18n from "i18next"; import i18n from "i18next";
import detector from "i18next-browser-languagedetector"; import detector from "i18next-browser-languagedetector";
import Backend from "i18next-http-backend"; import Backend from "i18next-http-backend";
@@ -8,6 +9,7 @@ interface Language {
name: string; name: string;
countryCode: string; countryCode: string;
fullCode: string; fullCode: string;
djs: () => Promise<ILocale>;
} }
/** /**
@@ -16,107 +18,128 @@ interface Language {
* name: Name of the language in the list * name: Name of the language in the list
* countryCode: Country code of the country's flag to display for this language * countryCode: Country code of the country's flag to display for this language
* fullCode: Full language code, used for Ant Design's locale * 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 } = { export const languages: { [key: string]: Language } = {
["en"]: { ["en"]: {
name: "English", name: "English",
countryCode: "gb", countryCode: "gb",
fullCode: "en-GB", fullCode: "en-GB",
djs: () => import("dayjs/locale/en"),
}, },
["sv"]: { ["sv"]: {
name: "Svenska", name: "Svenska",
countryCode: "se", countryCode: "se",
fullCode: "sv-SE", fullCode: "sv-SE",
djs: () => import("dayjs/locale/sv"),
}, },
["de"]: { ["de"]: {
name: "Deutsch", name: "Deutsch",
countryCode: "de", countryCode: "de",
fullCode: "de-DE", fullCode: "de-DE",
djs: () => import("dayjs/locale/de"),
}, },
["es"]: { ["es"]: {
name: "Español", name: "Español",
countryCode: "es", countryCode: "es",
fullCode: "es-ES", fullCode: "es-ES",
djs: () => import("dayjs/locale/es"),
}, },
["zh"]: { ["zh"]: {
name: "简体中文", name: "简体中文",
countryCode: "cn", countryCode: "cn",
fullCode: "zh-CN", fullCode: "zh-CN",
djs: () => import("dayjs/locale/zh-cn"),
}, },
["zh-Hant"]: { ["zh-Hant"]: {
name: "繁體中文", name: "繁體中文",
countryCode: "cn", countryCode: "cn",
fullCode: "zh-TW", fullCode: "zh-TW",
djs: () => import("dayjs/locale/zh-hk"),
}, },
["pl"]: { ["pl"]: {
name: "Polski", name: "Polski",
countryCode: "pl", countryCode: "pl",
fullCode: "pl-PL", fullCode: "pl-PL",
djs: () => import("dayjs/locale/pl"),
}, },
["ru"]: { ["ru"]: {
name: "Русский", name: "Русский",
countryCode: "ru", countryCode: "ru",
fullCode: "ru-RU", fullCode: "ru-RU",
djs: () => import("dayjs/locale/ru"),
}, },
["cs"]: { ["cs"]: {
name: "Česky", name: "Česky",
countryCode: "cz", countryCode: "cz",
fullCode: "cs-CZ", fullCode: "cs-CZ",
djs: () => import("dayjs/locale/cs"),
}, },
["nb-NO"]: { ["nb-NO"]: {
name: "Norsk bokmål", name: "Norsk bokmål",
countryCode: "no", countryCode: "no",
fullCode: "nb-NO", fullCode: "nb-NO",
djs: () => import("dayjs/locale/nb"),
}, },
["nl"]: { ["nl"]: {
name: "Nederlands", name: "Nederlands",
countryCode: "nl", countryCode: "nl",
fullCode: "nl-NL", fullCode: "nl-NL",
djs: () => import("dayjs/locale/nl"),
}, },
["fr"]: { ["fr"]: {
name: "Français", name: "Français",
countryCode: "fr", countryCode: "fr",
fullCode: "fr-FR", fullCode: "fr-FR",
djs: () => import("dayjs/locale/fr"),
}, },
["hu"]: { ["hu"]: {
name: "Magyar", name: "Magyar",
countryCode: "hu", countryCode: "hu",
fullCode: "hu-HU", fullCode: "hu-HU",
djs: () => import("dayjs/locale/hu"),
}, },
["it"]: { ["it"]: {
name: "Italiano", name: "Italiano",
countryCode: "it", countryCode: "it",
fullCode: "it-IT", fullCode: "it-IT",
djs: () => import("dayjs/locale/it"),
}, },
["uk"]: { ["uk"]: {
name: "Українська", name: "Українська",
countryCode: "ua", countryCode: "ua",
fullCode: "uk-UA", fullCode: "uk-UA",
djs: () => import("dayjs/locale/uk"),
}, },
["el"]: { ["el"]: {
name: "Ελληνικά", name: "Ελληνικά",
countryCode: "gr", countryCode: "gr",
fullCode: "el-GR", fullCode: "el-GR",
djs: () => import("dayjs/locale/el"),
}, },
["da"]: { ["da"]: {
name: "Dansk", name: "Dansk",
countryCode: "dk", countryCode: "dk",
fullCode: "da-DK", fullCode: "da-DK",
djs: () => import("dayjs/locale/da"),
}, },
["pt"]: { ["pt"]: {
name: "Português", name: "Português",
countryCode: "pt", countryCode: "pt",
fullCode: "pt-PT", fullCode: "pt-PT",
djs: () => import("dayjs/locale/pt"),
}, },
["fa"]: { ["fa"]: {
name: "فارسی", name: "فارسی",
countryCode: "ir", countryCode: "ir",
fullCode: "fa-IR", fullCode: "fa-IR",
djs: () => import("dayjs/locale/fa"),
}, },
["ro"]: { ["ro"]: {
name: "Român", name: "Român",
countryCode: "ro", countryCode: "ro",
fullCode: "ro-RO", fullCode: "ro-RO",
djs: () => import("dayjs/locale/ro"),
}, },
}; };
@@ -134,4 +157,8 @@ i18n
fallbackLng: "en", fallbackLng: "en",
}); });
i18n.on("languageChanged", function (lng) {
languages[lng].djs().then((djs) => dayjs.locale(djs.name));
});
export default i18n; export default i18n;

View File

@@ -41,7 +41,7 @@ export function LocationContainer() {
const grouped: Record<string, ISpool[]> = {}; const grouped: Record<string, ISpool[]> = {};
spools.forEach((spool) => { spools.forEach((spool) => {
const loc = spool.location ?? t("spool.no_location"); const loc = spool.location ?? t("locations.no_location");
if (!grouped[loc]) { if (!grouped[loc]) {
grouped[loc] = []; grouped[loc] = [];
} }
@@ -155,12 +155,13 @@ export function LocationContainer() {
} }
const addNewLocation = () => { const addNewLocation = () => {
let newLocationName = "New Location"; const baseLocationName = t("locations.new_location");
let newLocationName = baseLocationName;
const newLocs = [...locationsList]; const newLocs = [...locationsList];
let i = 1; let i = 1;
while (newLocs.includes(newLocationName)) { while (newLocs.includes(newLocationName)) {
newLocationName = "New Location " + i; newLocationName = baseLocationName + " " + i;
i++; i++;
} }
newLocs.push(newLocationName); newLocs.push(newLocationName);

View File

@@ -4,12 +4,18 @@ import type { Identifier, XYCoord } from "dnd-core";
import { useDrag, useDrop } from "react-dnd"; import { useDrag, useDrop } from "react-dnd";
import { EditOutlined, EyeOutlined } from "@ant-design/icons"; 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 { useEffect, useRef } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import SpoolIcon from "../../../components/spoolIcon"; import SpoolIcon from "../../../components/spoolIcon";
import { ISpool } from "../../spools/model"; import { ISpool } from "../../spools/model";
import { ItemTypes, SpoolDragItem, useCurrentDraggedSpool } from "../dnd"; import { ItemTypes, SpoolDragItem, useCurrentDraggedSpool } from "../dnd";
dayjs.extend(utc);
dayjs.extend(relativeTime);
const { useToken } = theme; const { useToken } = theme;
export function SpoolCard({ export function SpoolCard({
@@ -134,6 +140,21 @@ export function SpoolCard({
}; };
drag(drop(ref)); 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 ( return (
<div className="spool" ref={ref} style={style} data-handler-id={handlerId}> <div className="spool" ref={ref} style={style} data-handler-id={handlerId}>
<SpoolIcon color={colorObj} /> <SpoolIcon color={colorObj} />
@@ -157,7 +178,7 @@ export function SpoolCard({
color: token.colorTextSecondary, color: token.colorTextSecondary,
}} }}
> >
{spool.remaining_weight} / {spool.filament.weight} g {formatSubtitle(spool)}
</div> </div>
</div> </div>
</div> </div>