feat: add i18next, move attribute labels

feat/type-attributes
Tomáš Mládek 2022-10-25 21:47:17 +02:00
parent 4b3e7fc7a1
commit b0ef7f86cb
10 changed files with 103 additions and 35 deletions

View File

@ -39,6 +39,7 @@
"dompurify": "^2.3.4",
"filesize": "^8.0.6",
"history": "^5.1.0",
"i18next": "^22.0.2",
"lodash": "^4.17.21",
"lru-cache": "^6.0.0",
"marked": "^4.0.10",
@ -48,6 +49,7 @@
"sass": "^1.43.4",
"sirv-cli": "^1.0.0",
"sswr": "^1.3.1",
"svelte-i18next": "^1.2.2",
"svelte-navigator": "^3.1.5",
"three": "^0.136.0",
"upend": "../tools/upend_js",

View File

@ -7,6 +7,7 @@
import IconButton from "./utils/IconButton.svelte";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
import { i18n } from "../i18n";
export let entries: UpEntry[];
export let type: UpType | undefined = undefined;
@ -73,7 +74,7 @@
{#if type.name != "HIER"}
{type.label || type.name || "???"}
{:else}
Members
{$i18n.t("Members")}
{/if}
</UpLink>
{:else}

View File

@ -18,6 +18,7 @@
import { deleteEntry, putEntityAttribute, putEntry } from "../lib/api";
import Icon from "./utils/Icon.svelte";
import BlobViewer from "./display/BlobViewer.svelte";
import { i18n } from "../i18n";
const dispatch = createEventDispatcher();
const params = useParams();
@ -188,7 +189,7 @@
}
async function deleteObject() {
if (confirm(`Really delete "${identities.join(" | ")}"?`)) {
if (confirm(`${$i18n.t("Really delete")} "${identities.join(" | ")}"?`)) {
await deleteEntry(address);
dispatch("close");
}
@ -230,7 +231,7 @@
<div class="detail-col">
{#if groups?.length || editable}
<section class="groups labelborder">
<header><h3>Groups</h3></header>
<header><h3>{$i18n.t("Groups")}</h3></header>
<div class="content">
{#each groups as [entryAddress, address]}
<div class="group">
@ -282,7 +283,7 @@
{#if currentUntypedAttributes.length > 0 || editable}
<AttributeView
title="Other attributes"
title={$i18n.t("Other attributes")}
{editable}
entries={currentUntypedAttributes}
on:change={onChange}
@ -291,7 +292,9 @@
{#if currentBacklinks.length > 0}
<AttributeView
title={`Referred to (${$entity.backlinks.length})`}
title={`${$i18n.t("Referred to")} (${
$entity.backlinks.length
})`}
entries={currentBacklinks}
reverse
on:change={onChange}

View File

@ -15,6 +15,7 @@
import Icon from "../../utils/Icon.svelte";
import Selector from "../../utils/Selector.svelte";
import Spinner from "../../utils/Spinner.svelte";
import { i18n } from "../../../i18n";
export let address: string;
export let detail: boolean;
@ -204,7 +205,9 @@
});
try {
const peaksReq = await fetch(`${API_URL}/thumb/${address}?mime=audio&type=json`);
const peaksReq = await fetch(
`${API_URL}/thumb/${address}?mime=audio&type=json`
);
const peaks = await peaksReq.json();
wavesurfer.load(`${API_URL}/raw/${address}`, peaks.data);
} catch (e) {
@ -213,7 +216,9 @@
if (
(entity.get("FILE_SIZE") || 0) < 20_000_000 ||
confirm(
"File is large (>20 MiB) and UpEnd failed to load waveform from server. Generating the waveform locally may slow down your browser. Do you wish to proceed anyway?"
$i18n.t(
"File is large (>20 MiB) and UpEnd failed to load waveform from server. Generating the waveform locally may slow down your browser. Do you wish to proceed anyway?"
)
)
) {
console.warn(

View File

@ -15,6 +15,7 @@
import { defaultEntitySort, entityValueSort } from "../../util/sort";
import { attributeLabels } from "../../util/labels";
import { formatDuration } from "../../util/fragments/time";
import { i18n } from "../../i18n";
const dispatch = createEventDispatcher();
export let columns: string | undefined = undefined;
@ -49,7 +50,7 @@
newEntryValue = undefined;
}
async function removeEntry(address: string) {
if (confirm("Are you sure you want to remove the attribute?")) {
if (confirm($i18n.t("Are you sure you want to remove the attribute?"))) {
dispatch("change", { type: "delete", address } as AttributeChange);
}
}
@ -144,9 +145,9 @@
// Formatting & Display
const COLUMN_LABELS: { [key: string]: string } = {
entity: "Entity",
attribute: "Attribute",
value: "Value",
entity: $i18n.t("Entity"),
attribute: $i18n.t("Attribute"),
value: $i18n.t("Value"),
};
function formatValue(value: string | number, attribute: string): string {

14
webui/src/i18n/en.json Normal file
View File

@ -0,0 +1,14 @@
{
"attributes": {
"FILE_MIME": "MIME type",
"FILE_MTIME": "Last modified",
"FILE_SIZE": "File size",
"ADDED": "Added at",
"LAST_VISITED": "Last visited at",
"NUM_VISITED": "Times visited",
"LBL": "Label",
"IS": "Type",
"TYPE": "Type ID",
"MEDIA_DURATION": "Duration"
}
}

12
webui/src/i18n/index.ts Normal file
View File

@ -0,0 +1,12 @@
import i18next from "i18next";
import { createI18nStore } from "svelte-i18next";
import en from "./en.json";
i18next.init({
lng: "en",
resources: {
en,
},
});
export const i18n = createI18nStore(i18next);

View File

@ -1,23 +1,11 @@
import { readable, type Readable } from "svelte/store";
import { i18n } from "../i18n";
import { derived, readable, type Readable } from "svelte/store";
import { fetchAllAttributes } from "../lib/api";
const DEFAULT_ATTRIBUTE_LABELS = {
FILE_MIME: "MIME type",
FILE_MTIME: "Last modified",
FILE_SIZE: "File size",
ADDED: "Added at",
LAST_VISITED: "Last visited at",
NUM_VISITED: "Times visited",
LBL: "Label",
IS: "Type",
TYPE: "Type ID",
MEDIA_DURATION: "Duration",
};
export const attributeLabels: Readable<{ [key: string]: string }> = readable(
DEFAULT_ATTRIBUTE_LABELS,
const databaseAttributeLabels: Readable<{ [key: string]: string }> = readable(
{},
(set) => {
const result = Object.assign(DEFAULT_ATTRIBUTE_LABELS);
const result = {};
fetchAllAttributes().then((attributes) => {
attributes.forEach((attribute) => {
if (attribute.labels.length) {
@ -28,3 +16,13 @@ export const attributeLabels: Readable<{ [key: string]: string }> = readable(
});
}
);
export const attributeLabels: Readable<{ [key: string]: string }> = derived(
[i18n, databaseAttributeLabels],
([i18n, attributeLabels]) => {
const result = {};
Object.assign(result, i18n.getResourceBundle(i18n.language, "attributes"));
Object.assign(result, attributeLabels);
return result;
}
);

View File

@ -11,6 +11,7 @@
import { query } from "../lib/entity";
import { vaultInfo } from "../util/info";
import { updateTitle } from "../util/title";
import { i18n } from "../i18n";
const roots = (async () => {
const data = await fetchRoots();
@ -114,7 +115,7 @@
</h1>
<section class="roots">
<h2>Roots</h2>
<h2>{$i18n.t("Roots")}</h2>
{#await roots}
<Spinner centered />
{:then data}
@ -133,7 +134,7 @@
<div class="frecent">
{#if frequent.length || $frequentQuery === undefined}
<section class="frequent">
<h2>Frequently visited</h2>
<h2>{$i18n.t("Frequently visited")}</h2>
{#if $frequentQuery == undefined}
<Spinner centered />
{:else}
@ -147,7 +148,7 @@
{/if}
{#if recent.length || $recentQuery === undefined}
<section class="recent">
<h2>Recently visited</h2>
<h2>{$i18n.t("Recently visited")}</h2>
{#if $recentQuery == undefined}
<Spinner centered />
{:else}
@ -163,7 +164,7 @@
{#if latest.length || $latestQuery === undefined}
<section class="latest">
<h2>Most recently added</h2>
<h2>{$i18n.t("Most recently added")}</h2>
{#if $latestQuery == undefined}
<Spinner centered />
{:else}
@ -177,13 +178,14 @@
{/if}
<div class="button store-button">
<Link to="/store">View store statistics</Link>
<Link to="/store">{$i18n.t("View store statistics")}</Link>
</div>
<footer>
<div>
<strong>UpEnd</strong> - a database for the complex, the changing, and the
indeterminate
<strong>UpEnd</strong> - {$i18n.t(
"a database for the complex, the changing, and the indeterminate"
)}
</div>
<div>
<a target="_blank" href="https://upend.dev">

View File

@ -32,6 +32,15 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime@npm:^7.17.2":
version: 7.19.4
resolution: "@babel/runtime@npm:7.19.4"
dependencies:
regenerator-runtime: ^0.13.4
checksum: 66b7e3c13e9ee1d2c9397ea89144f29a875edee266a0eb2d9971be51b32fdbafc85808c7a45e011e6681899bb804b4e2ee2aed6dc07108dbbd6b11b6cc2afba6
languageName: node
linkType: hard
"@emotion/cache@npm:^11.4.0, @emotion/cache@npm:^11.7.1":
version: 11.7.1
resolution: "@emotion/cache@npm:11.7.1"
@ -1943,6 +1952,15 @@ __metadata:
languageName: node
linkType: hard
"i18next@npm:^22.0.2":
version: 22.0.2
resolution: "i18next@npm:22.0.2"
dependencies:
"@babel/runtime": ^7.17.2
checksum: d779ea7f8e35ad8fd9d38521b670dab9440c6f51eab4347800058ee311a5e55abacac7e55cb1ff9f6898b4222a5f2aac72175f0dc1faeffbf3b4b37800f8f924
languageName: node
linkType: hard
"iconv-lite@npm:^0.6.2":
version: 0.6.3
resolution: "iconv-lite@npm:0.6.3"
@ -3589,6 +3607,16 @@ __metadata:
languageName: node
linkType: hard
"svelte-i18next@npm:^1.2.2":
version: 1.2.2
resolution: "svelte-i18next@npm:1.2.2"
peerDependencies:
i18next: "*"
svelte: "*"
checksum: 24f9ed0f5f796124e075b39dc65a34fbcf625bfc6a26fdab15095f4e2c7c289ba5bf3d0c48ea644544376f9d9ffc1e523626f3cac219cd804ccffdcb28dff937
languageName: node
linkType: hard
"svelte-navigator@npm:^3.1.5":
version: 3.1.5
resolution: "svelte-navigator@npm:3.1.5"
@ -3964,6 +3992,7 @@ __metadata:
eslint-plugin-svelte3: ^4.0.0
filesize: ^8.0.6
history: ^5.1.0
i18next: ^22.0.2
lodash: ^4.17.21
lru-cache: ^6.0.0
marked: ^4.0.10
@ -3975,6 +4004,7 @@ __metadata:
sswr: ^1.3.1
svelte: ^3.49.0
svelte-check: ^2.8.0
svelte-i18next: ^1.2.2
svelte-navigator: ^3.1.5
svelte-preprocess: ^4.10.7
three: ^0.136.0