@eifi1/ui-kit 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -6
- package/dist/chart.d.ts +4 -1
- package/dist/components/amount-input.d.ts +4 -1
- package/dist/components/button-group.d.ts +4 -1
- package/dist/components/calculator.d.ts +4 -1
- package/dist/components/copy-button.d.ts +4 -1
- package/dist/components/facing-pair.d.ts +4 -1
- package/dist/components/file-button.d.ts +4 -1
- package/dist/components/file-dropzone.d.ts +4 -1
- package/dist/components/floating-panel.d.ts +4 -1
- package/dist/components/floating-panel.js +15 -1
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +4 -1
- package/dist/components/legal.d.ts +84 -0
- package/dist/components/legal.js +55 -0
- package/dist/components/legal.js.map +1 -0
- package/dist/components/money-field.d.ts +4 -1
- package/dist/components/number-field.d.ts +4 -1
- package/dist/components/number-input.d.ts +4 -1
- package/dist/components/numpad-sheet.d.ts +4 -1
- package/dist/components/series-chart.d.ts +4 -1
- package/dist/components/server-wake.d.ts +4 -2
- package/dist/components/server-wake.js +23 -2
- package/dist/components/server-wake.js.map +1 -1
- package/dist/components/settings-fields.d.ts +4 -1
- package/dist/components/share-card.d.ts +4 -1
- package/dist/components/text-link.d.ts +4 -1
- package/dist/components/time-input.d.ts +4 -1
- package/dist/components/translation-review-editor.d.ts +56 -0
- package/dist/components/translation-review-editor.js +159 -0
- package/dist/components/translation-review-editor.js.map +1 -0
- package/dist/components/translation-review-labels.d.ts +92 -0
- package/dist/components/translation-review-labels.js +67 -0
- package/dist/components/translation-review-labels.js.map +1 -0
- package/dist/components/translation-review-parts.d.ts +97 -0
- package/dist/components/translation-review-parts.js +163 -0
- package/dist/components/translation-review-parts.js.map +1 -0
- package/dist/components/translation-review.d.ts +94 -0
- package/dist/components/translation-review.js +344 -0
- package/dist/components/translation-review.js.map +1 -0
- package/dist/components/ui.d.ts +4 -1
- package/dist/hooks/use-file-drop.d.ts +4 -1
- package/dist/i18n/defaults.d.ts +4 -1
- package/dist/i18n/defaults.js +5 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +4 -1
- package/dist/i18n/german.js +51 -0
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +4 -1
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +268 -0
- package/dist/i18n/languages.js +112 -0
- package/dist/i18n/languages.js.map +1 -0
- package/dist/i18n/locales/de-CH.d.ts +4 -1
- package/dist/i18n/locales/en.d.ts +4 -1
- package/dist/i18n/locales/en.js +8 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +4 -1
- package/dist/i18n/locales/es.js +83 -33
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +4 -1
- package/dist/i18n/locales/fr.js +50 -0
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +4 -1
- package/dist/i18n/locales/hu.js +51 -0
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +4 -1
- package/dist/i18n/locales/it.js +90 -40
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +4 -1
- package/dist/i18n/locales/zh.js +51 -0
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/plurals.d.ts +23 -0
- package/dist/i18n/plurals.js +26 -0
- package/dist/i18n/plurals.js.map +1 -0
- package/dist/i18n/review.d.ts +102 -0
- package/dist/i18n/review.js +158 -0
- package/dist/i18n/review.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -1
- package/dist/{kit-labels-CPbsjlXr.d.ts → kit-labels-D1J2_jyD.d.ts} +7 -0
- package/dist/lib/floating-stack.d.ts +31 -0
- package/dist/lib/floating-stack.js +42 -0
- package/dist/lib/floating-stack.js.map +1 -0
- package/dist/lib/server-wake.d.ts +24 -8
- package/dist/lib/server-wake.js +6 -5
- package/dist/lib/server-wake.js.map +1 -1
- package/dist/lib/translation-review.d.ts +232 -0
- package/dist/lib/translation-review.js +194 -0
- package/dist/lib/translation-review.js.map +1 -0
- package/dist/rhf/fields.d.ts +4 -1
- package/dist/rhf/form.d.ts +4 -1
- package/dist/rhf.d.ts +4 -1
- package/dist/shell/app-shell.d.ts +4 -1
- package/dist/shell/top-bar-brand.d.ts +4 -1
- package/dist/shell.d.ts +4 -1
- package/dist/wizard/stepper-nav.d.ts +4 -1
- package/dist/wizard.d.ts +4 -1
- package/package.json +1 -1
- package/src/components/floating-panel.tsx +19 -1
- package/src/components/legal.tsx +159 -0
- package/src/components/server-wake.tsx +37 -5
- package/src/components/translation-review-editor.tsx +261 -0
- package/src/components/translation-review-labels.ts +152 -0
- package/src/components/translation-review-parts.tsx +269 -0
- package/src/components/translation-review.tsx +502 -0
- package/src/i18n/defaults.ts +4 -0
- package/src/i18n/german.ts +52 -0
- package/src/i18n/kit-labels.tsx +7 -0
- package/src/i18n/languages.ts +355 -0
- package/src/i18n/locales/en.ts +8 -0
- package/src/i18n/locales/es.ts +84 -33
- package/src/i18n/locales/fr.ts +51 -0
- package/src/i18n/locales/hu.ts +52 -0
- package/src/i18n/locales/it.ts +91 -40
- package/src/i18n/locales/zh.ts +52 -0
- package/src/i18n/plurals.ts +44 -0
- package/src/i18n/review.ts +193 -0
- package/src/index.ts +65 -0
- package/src/lib/floating-stack.ts +66 -0
- package/src/lib/server-wake.ts +34 -13
- package/src/lib/translation-review.ts +465 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import type { UiKitLabels } from "./kit-labels";
|
|
3
|
+
import type { LanguageOption } from "../shell/topbar-controls";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* "Which language is this reader in, and what does the kit say in it?" — the one
|
|
7
|
+
* answer the three apps each wrote for themselves (docs/i18n-harmonization.md, H2).
|
|
8
|
+
*
|
|
9
|
+
* kastlan, keksdose and Kurvenschmiede all ship a subset of the same seven languages,
|
|
10
|
+
* and every one of them hand-rolled the same three things: a list of languages with
|
|
11
|
+
* their names and flags, a rule for turning what a browser asks for (`de-AT`, `zh-TW`,
|
|
12
|
+
* `en-AU`) into a code the app ships, and a bridge from that code to the kit's label
|
|
13
|
+
* catalogue. The three disagreed where nobody was looking: a reader asking for `zh-TW`
|
|
14
|
+
* got German in kastlan and Chinese in Kurvenschmiede; keksdose's English got the raw
|
|
15
|
+
* defaults, so its counts read "12345" where the other two wrote "12,345"; kastlan
|
|
16
|
+
* formatted French as `fr-CH` and Kurvenschmiede as `fr-FR`. None of it was a decision.
|
|
17
|
+
* This module makes it one, in one place, tested once.
|
|
18
|
+
*
|
|
19
|
+
* WHAT IT IS NOT: an i18n library. It resolves no app strings, carries no detector and
|
|
20
|
+
* depends on nothing — the app keeps i18next (or whatever it uses) for its own words and
|
|
21
|
+
* asks this module only the questions the kit can answer for all three apps alike:
|
|
22
|
+
* - {@link KIT_LANGUAGES}: the seven codes, each with its own name, its English name,
|
|
23
|
+
* the flag it flies and the locale it formats in;
|
|
24
|
+
* - {@link resolveLanguage}: which of the codes an app offers answers a reader;
|
|
25
|
+
* - {@link formatLocaleOf}: the `Intl` tag for a code (the provider's `locale`,
|
|
26
|
+
* `<html lang>`, every `toLocaleString` in the app);
|
|
27
|
+
* - {@link loadUiKitLabels}: the kit's catalogue for a code, fetched on demand;
|
|
28
|
+
* - {@link languageOptions}: the rows `LanguageMenu` and `LanguageSetting` take.
|
|
29
|
+
*
|
|
30
|
+
* THE CODE AND THE FORMATTING LOCALE ARE TWO THINGS (Kurvenschmiede's insight, made
|
|
31
|
+
* the rule). The code names a catalogue — what the account stores, what i18next
|
|
32
|
+
* resolves, what a mail is chosen by — and a bare `en` says nothing about how a date is
|
|
33
|
+
* written. Each language formats like its home (Marcel, 2026-10-02, H6): `de-CH`,
|
|
34
|
+
* `en-GB` (British, like the spelling), `es-ES`, `fr-FR`, `it-IT`, `hu-HU`, `zh-CN`. An
|
|
35
|
+
* app that must format otherwise — kastlan's QR bill follows the SIX spec, not the
|
|
36
|
+
* reader — passes its own locale where it needs it; the registry is the default.
|
|
37
|
+
*
|
|
38
|
+
* THERE IS ONE GERMAN, `de-CH`: Swiss Standard German, formal (0.18). Every other
|
|
39
|
+
* German a browser or an old localStorage can hand over — `de`, `de-DE`, `de-AT`,
|
|
40
|
+
* `de-LI`, the briefly offered `de-informal` / `de-CH-informal` — is read as it.
|
|
41
|
+
* Its name is plain "Deutsch": it is the only German, so there is nothing to tell it
|
|
42
|
+
* apart from, and the Swiss flag beside it is what says where the spelling comes from.
|
|
43
|
+
*
|
|
44
|
+
* AND ONE CHINESE, `zh`, WHICH IS SIMPLIFIED. `zh-TW`, `zh-HK` and `zh-Hant` resolve to
|
|
45
|
+
* it: there is no Traditional catalogue, and a reader of Traditional characters reads
|
|
46
|
+
* Simplified far more readily than whichever second language a fallback would guess
|
|
47
|
+
* at (Kurvenschmiede's reasoning, now everyone's). Its name says "简体中文" so that
|
|
48
|
+
* reader sees what they are getting before they pick it.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/** The seven languages the kit ships a catalogue for. An app offers a subset. */
|
|
52
|
+
export type KitLanguageCode = "de-CH" | "en" | "es" | "fr" | "it" | "hu" | "zh";
|
|
53
|
+
|
|
54
|
+
/** One language the kit ships: what it is called, which flag it flies and which
|
|
55
|
+
* BCP 47 tag it formats in. */
|
|
56
|
+
export interface KitLanguage {
|
|
57
|
+
/** The catalogue code — what an app stores, sends and hands i18next. */
|
|
58
|
+
readonly code: KitLanguageCode;
|
|
59
|
+
/** The language's name in itself, for a switcher: the reader looking for theirs may
|
|
60
|
+
* not read the language the page is currently in. */
|
|
61
|
+
readonly nativeName: string;
|
|
62
|
+
/** The name in English, for places that list languages to a team rather than to a
|
|
63
|
+
* reader (a translation review's locale column, an admin's reviewer grant). */
|
|
64
|
+
readonly englishName: string;
|
|
65
|
+
/** The BCP 47 tag the language formats numbers and dates in, and declares itself in
|
|
66
|
+
* on `<html lang>`. See {@link formatLocaleOf}. */
|
|
67
|
+
readonly formatLocale: string;
|
|
68
|
+
/** ISO 3166-1 alpha-2 country (lowercase) for flag-icons' `fi fi-<flag>` class —
|
|
69
|
+
* NOT the language code: English flies the UK flag, German the Swiss one, Chinese
|
|
70
|
+
* the PRC's (the catalogue is Simplified). The kit renders the class name only; the
|
|
71
|
+
* app supplies the flag stylesheet (README, "What you install alongside it"). */
|
|
72
|
+
readonly flag: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Every language the kit ships, in the order a switcher lists them: alphabetical by
|
|
77
|
+
* native name (Deutsch, English, Español … 简体中文), which is what a reader scanning for
|
|
78
|
+
* their own language's name expects.
|
|
79
|
+
*
|
|
80
|
+
* An app offering four of them passes those four codes to {@link resolveLanguage} and
|
|
81
|
+
* {@link languageOptions}; it does not copy this list. A language added here is one the
|
|
82
|
+
* kit has a catalogue for (the loader map below is typed by {@link KitLanguageCode}, so
|
|
83
|
+
* a code without one does not compile).
|
|
84
|
+
*/
|
|
85
|
+
export const KIT_LANGUAGES: readonly KitLanguage[] = [
|
|
86
|
+
{ code: "de-CH", nativeName: "Deutsch", englishName: "German", formatLocale: "de-CH", flag: "ch" },
|
|
87
|
+
{ code: "en", nativeName: "English", englishName: "English", formatLocale: "en-GB", flag: "gb" },
|
|
88
|
+
{ code: "es", nativeName: "Español", englishName: "Spanish", formatLocale: "es-ES", flag: "es" },
|
|
89
|
+
{ code: "fr", nativeName: "Français", englishName: "French", formatLocale: "fr-FR", flag: "fr" },
|
|
90
|
+
{ code: "it", nativeName: "Italiano", englishName: "Italian", formatLocale: "it-IT", flag: "it" },
|
|
91
|
+
{ code: "hu", nativeName: "Magyar", englishName: "Hungarian", formatLocale: "hu-HU", flag: "hu" },
|
|
92
|
+
{ code: "zh", nativeName: "简体中文", englishName: "Chinese (Simplified)", formatLocale: "zh-CN", flag: "cn" },
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const ALL_CODES: readonly KitLanguageCode[] = KIT_LANGUAGES.map((language) => language.code);
|
|
96
|
+
|
|
97
|
+
/** The kit's home language: what a reader nothing else matches gets, when it is offered. */
|
|
98
|
+
const HOME: KitLanguageCode = "de-CH";
|
|
99
|
+
|
|
100
|
+
/** The language part of a tag, lower-cased: `de-CH-informal` → `de`, `zh_Hant` → `zh`. */
|
|
101
|
+
const languagePart = (tag: string) => tag.split("-")[0].toLowerCase();
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The offered code that answers ONE requested tag, or `undefined`.
|
|
105
|
+
*
|
|
106
|
+
* Exact first, ignoring case (`DE-ch` → `de-CH`), then by language part: since the kit
|
|
107
|
+
* has exactly one code per language, "the offered code whose language is the tag's
|
|
108
|
+
* language" is the whole of the rule, and it is what takes every German to `de-CH`
|
|
109
|
+
* (`de-AT`, `de-informal`), every Chinese to `zh` (`zh-TW`, `zh-Hant-HK`) and every
|
|
110
|
+
* regional form to its base (`fr-CA` → `fr`, `en-AU` → `en`). A language the app does
|
|
111
|
+
* not offer answers nothing, so the caller moves on to the reader's next preference
|
|
112
|
+
* rather than landing on a guess. Underscores are read as hyphens (`de_CH`), the way a
|
|
113
|
+
* Java or Python backend may have stored the account's locale.
|
|
114
|
+
*/
|
|
115
|
+
function answer(
|
|
116
|
+
candidate: unknown,
|
|
117
|
+
offered: readonly KitLanguageCode[],
|
|
118
|
+
): KitLanguageCode | undefined {
|
|
119
|
+
if (typeof candidate !== "string") return undefined;
|
|
120
|
+
const tag = candidate.trim().replace(/_/g, "-");
|
|
121
|
+
if (!tag) return undefined;
|
|
122
|
+
const lower = tag.toLowerCase();
|
|
123
|
+
const exact = offered.find((code) => code.toLowerCase() === lower);
|
|
124
|
+
if (exact) return exact;
|
|
125
|
+
const language = languagePart(tag);
|
|
126
|
+
return offered.find((code) => languagePart(code) === language);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Which of the codes an app offers answers this reader.
|
|
131
|
+
*
|
|
132
|
+
* `candidates` are the reader's preferences **in precedence order**, and the first
|
|
133
|
+
* one that matches wins — so the order is the policy. The one the harmonisation plan
|
|
134
|
+
* gives every app (H5):
|
|
135
|
+
*
|
|
136
|
+
* resolveLanguage(
|
|
137
|
+
* [localStorage.getItem("<app>-lang"), user?.locale, ...navigator.languages],
|
|
138
|
+
* OFFERED,
|
|
139
|
+
* );
|
|
140
|
+
*
|
|
141
|
+
* the choice stored on this device (the reader picked it, here) → the account's
|
|
142
|
+
* language (read back on sign-in, so a new device starts in it) → the browser's list,
|
|
143
|
+
* in its own order → the fallback. Missing sources are simply `null` / `undefined`;
|
|
144
|
+
* they are skipped, as is an empty string.
|
|
145
|
+
*
|
|
146
|
+
* Each candidate is matched exactly (case-insensitively), then by language — every
|
|
147
|
+
* German variant to `de-CH`, every Chinese to `zh` (Simplified), any other regional
|
|
148
|
+
* form to its base language (`fr-CA` → `fr`, `en-AU` → `en`) — and only within
|
|
149
|
+
* `offered`: a reader whose browser says `es-MX` first and `en` second, in an app that
|
|
150
|
+
* offers no Spanish, gets English rather than the fallback. That is the difference
|
|
151
|
+
* from a single-tag lookup, and why the browser's whole list is passed, not its first
|
|
152
|
+
* entry.
|
|
153
|
+
*
|
|
154
|
+
* `fallback` is what a reader nothing matches gets: by default `de-CH` when the app
|
|
155
|
+
* offers it — the kit's and the three apps' home language — else the app's first
|
|
156
|
+
* offered code. It is returned as given, offered or not; the app chose it.
|
|
157
|
+
*
|
|
158
|
+
* Why not i18next's own matcher (`getBestMatchFromCodes`, which Kurvenschmiede calls):
|
|
159
|
+
* it needs an i18next instance, it reduces `de-AT` to a `de` none of the apps ships
|
|
160
|
+
* unless every caller remembers a converter first, and its answer to a list depends on
|
|
161
|
+
* `supportedLngs` and `nonExplicitSupportedLngs` being set exactly so. This is a pure
|
|
162
|
+
* function of its arguments, so the switcher, the account form, a server-rendered
|
|
163
|
+
* guess and a test all get the same answer without one.
|
|
164
|
+
*/
|
|
165
|
+
export function resolveLanguage(
|
|
166
|
+
candidates: readonly (string | null | undefined)[],
|
|
167
|
+
offered: readonly KitLanguageCode[],
|
|
168
|
+
fallback?: KitLanguageCode,
|
|
169
|
+
): KitLanguageCode {
|
|
170
|
+
for (const candidate of candidates) {
|
|
171
|
+
const found = answer(candidate, offered);
|
|
172
|
+
if (found) return found;
|
|
173
|
+
}
|
|
174
|
+
return fallback ?? (offered.includes(HOME) ? HOME : (offered[0] ?? HOME));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The BCP 47 tag a language formats in — `formatLocaleOf("en")` is `"en-GB"`.
|
|
179
|
+
*
|
|
180
|
+
* For `<UiKitProvider locale>`, for `<html lang>` (a screen reader pronounces by it,
|
|
181
|
+
* and `en` alone does not say which English) and for the app's own `Intl` calls, so
|
|
182
|
+
* the kit's counts and the app's amounts group their digits the same way.
|
|
183
|
+
*
|
|
184
|
+
* Takes any tag, not only a {@link KitLanguageCode}. A tag of one of the seven
|
|
185
|
+
* languages formats in that language's home, by decision — not the reader's region:
|
|
186
|
+
* `de-AT` as `de-CH`, `en-AU` and `en-US` as `en-GB`, `zh-TW` as `zh-CN`. So an app can
|
|
187
|
+
* hand it i18next's `resolvedLanguage` as it comes.
|
|
188
|
+
*
|
|
189
|
+
* A tag of a language the kit does NOT ship formats as itself (canonicalised): `sv-SE`
|
|
190
|
+
* stays `sv-SE`. That is a formatting preference, not a language — keksdose's date
|
|
191
|
+
* setting offers ISO order as `sv-SE`, and 0.19.0 turned it into a German weekday by
|
|
192
|
+
* resolving it to the home language (keksdose's 0.19 adoption). Words are another
|
|
193
|
+
* matter: {@link resolveLanguage} and {@link loadUiKitLabels} still fall back to
|
|
194
|
+
* `de-CH` for such a tag. A tag `Intl` cannot read formats as `de-CH`.
|
|
195
|
+
*/
|
|
196
|
+
export function formatLocaleOf(code: string): string {
|
|
197
|
+
const shipped = answer(code, ALL_CODES);
|
|
198
|
+
if (shipped) return homeFormat(shipped);
|
|
199
|
+
try {
|
|
200
|
+
return Intl.getCanonicalLocales(code)[0] ?? homeFormat(HOME);
|
|
201
|
+
} catch {
|
|
202
|
+
return homeFormat(HOME);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function homeFormat(code: KitLanguageCode): string {
|
|
207
|
+
return KIT_LANGUAGES.find((language) => language.code === code)!.formatLocale;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** The format tag of the catalogue a code loads — always one of the seven languages'
|
|
211
|
+
* own, so the words and the digits of a catalogue agree. */
|
|
212
|
+
const catalogueFormat = (code: string) => homeFormat(resolveLanguage([code], ALL_CODES));
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* One loader per catalogue, each a LITERAL relative `import()`.
|
|
216
|
+
*
|
|
217
|
+
* The literal is load-bearing twice. The library is built one file per module
|
|
218
|
+
* (`bundle: false`, tsup.config.ts), so `./locales/fr` stays a module of its own in
|
|
219
|
+
* dist/ and `scripts/fix-esm-extensions.mjs` gives the specifier its `.js`. And an
|
|
220
|
+
* app's bundler can only split what it can read: a path assembled from a code is a
|
|
221
|
+
* chunk rollup cannot make, and the failure is at runtime. With literals, each
|
|
222
|
+
* language is a chunk of its own and a reader downloads exactly theirs — about 25 kB of
|
|
223
|
+
* JavaScript a catalogue, so an entry chunk holding all of them would carry six that
|
|
224
|
+
* the reader never uses.
|
|
225
|
+
*
|
|
226
|
+
* Relative, not `@eifi1/ui-kit/i18n/fr`: a package importing itself by name resolves
|
|
227
|
+
* through the consumer's node_modules, which a linked checkout or a test of this repo
|
|
228
|
+
* does not have.
|
|
229
|
+
*/
|
|
230
|
+
const LOADERS: Record<KitLanguageCode, (formatLocale: string) => Promise<UiKitLabels>> = {
|
|
231
|
+
"de-CH": async (locale) => (await import("./locales/de-CH")).uiKitLabelsDeCh(locale),
|
|
232
|
+
en: async (locale) => (await import("./locales/en")).uiKitLabelsEn(locale),
|
|
233
|
+
es: async (locale) => (await import("./locales/es")).uiKitLabelsEs(locale),
|
|
234
|
+
fr: async (locale) => (await import("./locales/fr")).uiKitLabelsFr(locale),
|
|
235
|
+
it: async (locale) => (await import("./locales/it")).uiKitLabelsIt(locale),
|
|
236
|
+
hu: async (locale) => (await import("./locales/hu")).uiKitLabelsHu(locale),
|
|
237
|
+
zh: async (locale) => (await import("./locales/zh")).uiKitLabelsZh(locale),
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/** One promise per (language, format locale), so asking twice is one fetch and one
|
|
241
|
+
* object: the provider's context value keeps its identity, and every kit component
|
|
242
|
+
* reading it is spared a re-render on a switch back. */
|
|
243
|
+
const loaded = new Map<string, Promise<UiKitLabels>>();
|
|
244
|
+
/** The same catalogues once they have arrived — what {@link peekUiKitLabels} reads. */
|
|
245
|
+
const ready = new Map<string, UiKitLabels>();
|
|
246
|
+
|
|
247
|
+
function cacheKey(code: string, formatLocale: string): [KitLanguageCode, string] {
|
|
248
|
+
const language = resolveLanguage([code], ALL_CODES);
|
|
249
|
+
return [language, `${language}\u0000${formatLocale}`];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* The kit's catalogue for a language, fetched on demand and formatted in
|
|
254
|
+
* `formatLocale` — for `<UiKitProvider labels={…}>`.
|
|
255
|
+
*
|
|
256
|
+
* Every language goes through its catalogue's factory, English included:
|
|
257
|
+
* `uiKitLabelsEn("en-GB")` writes "12,345 rows" where the bare
|
|
258
|
+
* `DEFAULT_UI_KIT_LABELS` print "12345" (and keksdose's English did, by passing no
|
|
259
|
+
* labels at all). `formatLocale` defaults to the language's own
|
|
260
|
+
* ({@link formatLocaleOf}); pass another only for a reason, as the factories' own
|
|
261
|
+
* docs describe (`"de-DE"` keeps the Swiss words and writes German digits).
|
|
262
|
+
*
|
|
263
|
+
* `code` may be any tag and is resolved like {@link formatLocaleOf}'s, so `de` loads
|
|
264
|
+
* the one German and a language the kit does not ship loads `de-CH` — the same answer
|
|
265
|
+
* the app's own fallback gives, rather than English beside the app's German.
|
|
266
|
+
*
|
|
267
|
+
* Asynchronous on purpose, and there is no synchronous twin: one would have to import
|
|
268
|
+
* all seven catalogues statically, which puts every language in every app's entry
|
|
269
|
+
* chunk — exactly what one subpath per language exists to prevent. Await it before the
|
|
270
|
+
* first render, beside the app's own catalogue (the apps already await i18next's init
|
|
271
|
+
* for the same reason: no frame in the wrong language), and keep the result in state.
|
|
272
|
+
* To change a few words, spread over what it returns rather than copying a catalogue:
|
|
273
|
+
* a copy misses every key a later release adds.
|
|
274
|
+
*
|
|
275
|
+
* A rejected fetch (a deploy replaced the chunk, the device went offline) is not
|
|
276
|
+
* remembered, so the next call tries again.
|
|
277
|
+
*/
|
|
278
|
+
export function loadUiKitLabels(
|
|
279
|
+
code: string,
|
|
280
|
+
formatLocale: string = catalogueFormat(code),
|
|
281
|
+
): Promise<UiKitLabels> {
|
|
282
|
+
const [language, key] = cacheKey(code, formatLocale);
|
|
283
|
+
let labels = loaded.get(key);
|
|
284
|
+
if (!labels) {
|
|
285
|
+
labels = LOADERS[language](formatLocale);
|
|
286
|
+
loaded.set(key, labels);
|
|
287
|
+
labels.then(
|
|
288
|
+
(done) => ready.set(key, done),
|
|
289
|
+
() => loaded.delete(key),
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
return labels;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* The catalogue {@link loadUiKitLabels} already fetched for this language and format
|
|
297
|
+
* locale — synchronously — or `undefined` while it has not arrived. For the provider's
|
|
298
|
+
* render, which cannot await: kastlan wrapped the loader in a cache of its own
|
|
299
|
+
* (`hasKitLabels` / `kitLabelsFor`) to get exactly this, and the other apps would have
|
|
300
|
+
* written the same lines. Same object as the promise resolved to.
|
|
301
|
+
*/
|
|
302
|
+
export function peekUiKitLabels(code: string, formatLocale: string = catalogueFormat(code)): UiKitLabels | undefined {
|
|
303
|
+
return ready.get(cacheKey(code, formatLocale)[1]);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* The kit's catalogue for `code`, loading it on first use: `<UiKitProvider
|
|
308
|
+
* labels={useUiKitLabels(language)} locale={formatLocaleOf(language)}>`.
|
|
309
|
+
*
|
|
310
|
+
* On a switch it keeps returning the PREVIOUS language's catalogue until the next one
|
|
311
|
+
* has arrived, so the kit's words never blank out mid-switch; `undefined` only before
|
|
312
|
+
* the very first catalogue (render nothing, or the English defaults, until then — or
|
|
313
|
+
* await {@link loadUiKitLabels} before the first render, as the apps do for their own
|
|
314
|
+
* catalogue). A failed fetch keeps the previous catalogue; the next switch tries again.
|
|
315
|
+
*/
|
|
316
|
+
export function useUiKitLabels(code: string, formatLocale: string = catalogueFormat(code)): UiKitLabels | undefined {
|
|
317
|
+
const current = peekUiKitLabels(code, formatLocale);
|
|
318
|
+
const [previous, setPrevious] = useState<UiKitLabels | undefined>(current);
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
if (peekUiKitLabels(code, formatLocale)) return;
|
|
321
|
+
let live = true;
|
|
322
|
+
loadUiKitLabels(code, formatLocale).then(
|
|
323
|
+
(labels) => {
|
|
324
|
+
if (live) setPrevious(labels);
|
|
325
|
+
},
|
|
326
|
+
() => {},
|
|
327
|
+
);
|
|
328
|
+
return () => {
|
|
329
|
+
live = false;
|
|
330
|
+
};
|
|
331
|
+
}, [code, formatLocale]);
|
|
332
|
+
if (current && current !== previous) setPrevious(current);
|
|
333
|
+
return current ?? previous;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* The rows `LanguageMenu` and `LanguageSetting` take, for the codes an app offers, in
|
|
338
|
+
* the order given: `{ code, label: nativeName, country: flag }`.
|
|
339
|
+
*
|
|
340
|
+
* `LanguageMenu` draws `country` as a flag-icons class (`fi fi-ch`) and nothing more —
|
|
341
|
+
* the kit ships no flag artwork and no flag CSS, the app loads the stylesheet (or just
|
|
342
|
+
* the rules for its flags, as Kurvenschmiede and keksdose do). `LanguageSetting` is a
|
|
343
|
+
* native `<select>`, whose options can hold text only, so it shows the names and
|
|
344
|
+
* ignores `country`; the same array serves both.
|
|
345
|
+
*
|
|
346
|
+
* A label of the app's own (keksdose's "Deutsch (Schweiz)") is a `.map` over the
|
|
347
|
+
* result.
|
|
348
|
+
*/
|
|
349
|
+
export function languageOptions(codes: readonly KitLanguageCode[] = ALL_CODES): LanguageOption[] {
|
|
350
|
+
return codes.flatMap((code) => {
|
|
351
|
+
const language = KIT_LANGUAGES.find((entry) => entry.code === code);
|
|
352
|
+
if (!language) return [];
|
|
353
|
+
return [{ code: language.code, label: language.nativeName, country: language.flag }];
|
|
354
|
+
});
|
|
355
|
+
}
|
package/src/i18n/locales/en.ts
CHANGED
|
@@ -112,5 +112,13 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
|
|
|
112
112
|
counter: (index, count) => `${n(index)} / ${n(count)}`,
|
|
113
113
|
position: (index, count) => `Image ${n(index)} of ${n(count)}`,
|
|
114
114
|
},
|
|
115
|
+
translationReview: {
|
|
116
|
+
...d.translationReview,
|
|
117
|
+
filterCount: (label, count) => `${label} · ${n(count)}`,
|
|
118
|
+
placeholdersOnly: (count) => `Only placeholder problems (${n(count)})`,
|
|
119
|
+
progress: (approved, total) => `${n(approved)} of ${n(total)} approved`,
|
|
120
|
+
localeProgress: (approved, total) => `${n(approved)}/${n(total)}`,
|
|
121
|
+
exportCorrections: (count) => `Export corrections (${n(count)})`,
|
|
122
|
+
},
|
|
115
123
|
};
|
|
116
124
|
}
|
package/src/i18n/locales/es.ts
CHANGED
|
@@ -19,12 +19,12 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
19
19
|
feedbackAttachment: {
|
|
20
20
|
attachmentAdd: "Adjuntar imagen",
|
|
21
21
|
attachmentCapture: "Capturar pantalla",
|
|
22
|
-
attachmentPaste: "…o
|
|
22
|
+
attachmentPaste: "…o pegue una captura de pantalla desde el portapapeles.",
|
|
23
23
|
attachmentRemove: "Quitar adjunto",
|
|
24
24
|
attachmentList: "Adjuntos",
|
|
25
25
|
attachmentScreenshot: "Captura de pantalla",
|
|
26
26
|
attachmentRemoveFile: (name) => `Quitar ${name}`,
|
|
27
|
-
attachmentLimit: (max) => `Máximo ${n(max)} ${max === 1 ? "adjunto" : "adjuntos"}:
|
|
27
|
+
attachmentLimit: (max) => `Máximo ${n(max)} ${max === 1 ? "adjunto" : "adjuntos"}: quite uno para añadir otro.`,
|
|
28
28
|
},
|
|
29
29
|
feedbackDialog: {
|
|
30
30
|
title: "Enviar comentarios",
|
|
@@ -41,13 +41,13 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
41
41
|
thread: "Comentarios",
|
|
42
42
|
empty: "Aún no hay comentarios",
|
|
43
43
|
loading: "Cargando comentarios…",
|
|
44
|
-
you: "
|
|
44
|
+
you: "Usted",
|
|
45
45
|
staff: "Equipo",
|
|
46
46
|
attachments: "Adjuntos",
|
|
47
47
|
},
|
|
48
48
|
feedbackComposer: {
|
|
49
|
-
field: "
|
|
50
|
-
placeholder: "
|
|
49
|
+
field: "Escriba un comentario",
|
|
50
|
+
placeholder: "Escriba un comentario…",
|
|
51
51
|
send: "Enviar",
|
|
52
52
|
sendHint: (modifier) => `${modifier} + Intro para enviar`,
|
|
53
53
|
sendHintEnter: "Intro para enviar, Mayús + Intro para una nueva línea",
|
|
@@ -75,23 +75,23 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
75
75
|
enabledText: "Activada",
|
|
76
76
|
disabledText: "Desactivada",
|
|
77
77
|
enable: "Configurar la verificación en dos pasos",
|
|
78
|
-
scanHint: "
|
|
78
|
+
scanHint: "Escanee este código con su app de autenticación e introduzca el código que muestra.",
|
|
79
79
|
codeLabel: "Código de verificación",
|
|
80
80
|
verify: "Verificar y activar",
|
|
81
81
|
disableSection: "Desactivar la verificación en dos pasos",
|
|
82
82
|
password: "Contraseña actual",
|
|
83
83
|
disable: "Desactivar",
|
|
84
|
-
qrAlt: "Código QR para
|
|
85
|
-
secretHint: "¿No
|
|
84
|
+
qrAlt: "Código QR para su app de autenticación",
|
|
85
|
+
secretHint: "¿No puede escanearlo? Introduzca esta clave en la app:",
|
|
86
86
|
copySecret: "Copiar clave",
|
|
87
87
|
},
|
|
88
88
|
passkeys: {
|
|
89
89
|
title: "Llaves de acceso",
|
|
90
|
-
description: "
|
|
91
|
-
descriptionAlongside: "
|
|
90
|
+
description: "Inicie sesión con su huella, su cara o el PIN del dispositivo en lugar de una contraseña.",
|
|
91
|
+
descriptionAlongside: "Inicie sesión con su huella, su cara o el PIN del dispositivo. Su contraseña sigue funcionando.",
|
|
92
92
|
empty: "Aún no hay llaves de acceso",
|
|
93
93
|
loading: "Cargando llaves de acceso…",
|
|
94
|
-
list: "
|
|
94
|
+
list: "Sus llaves de acceso",
|
|
95
95
|
nameLabel: "Nombre (opcional)",
|
|
96
96
|
namePlaceholder: "p. ej. Portátil, Móvil",
|
|
97
97
|
add: "Añadir llave de acceso",
|
|
@@ -103,7 +103,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
103
103
|
cancel: "Cancelar",
|
|
104
104
|
delete: "Eliminar",
|
|
105
105
|
deleteItem: (name) => `Eliminar ${name}`,
|
|
106
|
-
deleteConfirm: (name) => `¿Eliminar «${name}»? Ya no
|
|
106
|
+
deleteConfirm: (name) => `¿Eliminar «${name}»? Ya no podrá iniciar sesión con ella.`,
|
|
107
107
|
confirmDelete: "Eliminar llave de acceso",
|
|
108
108
|
created: (date) => `Añadida el ${date}`,
|
|
109
109
|
lastUsed: (date) => `Último uso: ${date}`,
|
|
@@ -117,12 +117,12 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
117
117
|
addRow: "Añadir fila",
|
|
118
118
|
removeRow: (row) => `Quitar la fila ${row}`,
|
|
119
119
|
clear: "Vaciar tabla",
|
|
120
|
-
pasteHint: "
|
|
120
|
+
pasteHint: "Pegue un bloque de una hoja de cálculo en cualquier celda",
|
|
121
121
|
cell: (column, row) => `${column}, fila ${row}`,
|
|
122
122
|
rowNumber: "Fila",
|
|
123
123
|
rowActions: "Acciones de fila",
|
|
124
124
|
keyboardHint:
|
|
125
|
-
"Las flechas mueven entre celdas.
|
|
125
|
+
"Las flechas mueven entre celdas. Escriba para reemplazar una celda, F2 para editarla, Escape para deshacer la edición. Intro baja y añade una fila al final.",
|
|
126
126
|
lineError: (line) => `No se pudo leer la línea ${line}`,
|
|
127
127
|
points: (count) => (count === 1 ? "1 punto" : `${count} puntos`),
|
|
128
128
|
problems: (count) =>
|
|
@@ -148,7 +148,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
148
148
|
dataTable: {
|
|
149
149
|
columns: "Columnas",
|
|
150
150
|
selectAllRows: "Seleccionar todas las filas",
|
|
151
|
-
sortHint: "
|
|
151
|
+
sortHint: "Haga clic para ordenar · Mayús+clic para añadir un criterio",
|
|
152
152
|
filter: "Filtrar",
|
|
153
153
|
close: "Cerrar",
|
|
154
154
|
selectRow: "Seleccionar fila",
|
|
@@ -207,13 +207,13 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
207
207
|
nextMonth: "Mes siguiente",
|
|
208
208
|
// Already formatted in the provider's locale ("lunes, 14 de septiembre de 2026").
|
|
209
209
|
day: (date) => date,
|
|
210
|
-
chooseStart: "
|
|
211
|
-
chooseEnd: "
|
|
210
|
+
chooseStart: "Elija una fecha de inicio",
|
|
211
|
+
chooseEnd: "Elija una fecha de fin",
|
|
212
212
|
// "Fecha" is feminine; the date follows a label rather than being the subject of
|
|
213
213
|
// a participle it cannot agree with.
|
|
214
|
-
startSelected: (date) => `Fecha de inicio: ${date}.
|
|
214
|
+
startSelected: (date) => `Fecha de inicio: ${date}. Elija una fecha de fin.`,
|
|
215
215
|
rangeSelected: (from, to) =>
|
|
216
|
-
`Periodo seleccionado: del ${from} al ${to}.
|
|
216
|
+
`Periodo seleccionado: del ${from} al ${to}. Elija una fecha de inicio para empezar de nuevo.`,
|
|
217
217
|
},
|
|
218
218
|
monthPicker: {
|
|
219
219
|
previousYear: "Año anterior",
|
|
@@ -256,7 +256,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
256
256
|
loadError: "No se han podido cargar los resultados",
|
|
257
257
|
resultCount: (count) => `${n(count)} ${plural(count, "resultado", "resultados")}`,
|
|
258
258
|
minChars: (count) =>
|
|
259
|
-
`
|
|
259
|
+
`Escriba al menos ${n(count)} ${plural(count, "carácter", "caracteres")}`,
|
|
260
260
|
},
|
|
261
261
|
multiSelect: {
|
|
262
262
|
search: "Buscar",
|
|
@@ -319,7 +319,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
319
319
|
cancel: "Cancelar",
|
|
320
320
|
prompt: "Esta acción no se puede deshacer.",
|
|
321
321
|
password: "Contraseña",
|
|
322
|
-
phrase: (phrase) => `
|
|
322
|
+
phrase: (phrase) => `Escriba «${phrase}» para confirmar`,
|
|
323
323
|
},
|
|
324
324
|
tabs: {
|
|
325
325
|
add: "Añadir pestaña",
|
|
@@ -395,7 +395,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
395
395
|
cancelDismissLabel: "Seguir editando",
|
|
396
396
|
reviewTitle: "Revisión",
|
|
397
397
|
edit: "Editar",
|
|
398
|
-
missingRequired: "
|
|
398
|
+
missingRequired: "Rellene todos los campos obligatorios.",
|
|
399
399
|
genericError: "Se ha producido un error",
|
|
400
400
|
},
|
|
401
401
|
tour: {
|
|
@@ -403,7 +403,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
403
403
|
back: "Atrás",
|
|
404
404
|
skip: "Omitir",
|
|
405
405
|
done: "Listo",
|
|
406
|
-
awaitClickHint: "
|
|
406
|
+
awaitClickHint: "Haga clic en el elemento resaltado para continuar",
|
|
407
407
|
step: (current, total) => `${n(current)} / ${n(total)}`,
|
|
408
408
|
},
|
|
409
409
|
commandPalette: {
|
|
@@ -442,8 +442,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
442
442
|
},
|
|
443
443
|
signaturePad: {
|
|
444
444
|
label: "Firma",
|
|
445
|
-
instructions: "
|
|
446
|
-
typedFallbackHint: "Si no
|
|
445
|
+
instructions: "Firme en el recuadro con el ratón, el dedo o un lápiz.",
|
|
446
|
+
typedFallbackHint: "Si no puede dibujar, escriba su nombre.",
|
|
447
447
|
empty: "Aún no hay nada dibujado",
|
|
448
448
|
signed: "Firma dibujada",
|
|
449
449
|
undo: "Deshacer el último trazo",
|
|
@@ -481,7 +481,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
481
481
|
seriesChart: {
|
|
482
482
|
resetZoom: "Restablecer zoom",
|
|
483
483
|
zoomHint:
|
|
484
|
-
"
|
|
484
|
+
"Arrastre para ampliar: una selección más o menos cuadrada amplía ambos ejes; una larga y estrecha, solo el suyo. Haga doble clic para restablecer.",
|
|
485
485
|
empty: "Sin datos",
|
|
486
486
|
legend: "Series",
|
|
487
487
|
points: "Valores del gráfico",
|
|
@@ -496,7 +496,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
496
496
|
confirmDialog: {
|
|
497
497
|
confirm: "Confirmar",
|
|
498
498
|
cancel: "Cancelar",
|
|
499
|
-
typed: (text) => `
|
|
499
|
+
typed: (text) => `Escriba «${text}» para confirmar`,
|
|
500
500
|
},
|
|
501
501
|
floatingPanel: {
|
|
502
502
|
close: "Cerrar",
|
|
@@ -533,7 +533,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
533
533
|
lineItems: {
|
|
534
534
|
add: "Añadir fila",
|
|
535
535
|
remove: (row) => `Eliminar fila ${row}`,
|
|
536
|
-
confirmRemove: (row) => `¿Eliminar fila ${row}?
|
|
536
|
+
confirmRemove: (row) => `¿Eliminar fila ${row}? Pulse de nuevo para confirmar`,
|
|
537
537
|
row: (row) => `Fila ${row}`,
|
|
538
538
|
cell: (column, row) => `${column}, fila ${row}`,
|
|
539
539
|
totals: "Total",
|
|
@@ -583,7 +583,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
583
583
|
openInNewTab: "Abrir en una pestaña nueva",
|
|
584
584
|
},
|
|
585
585
|
writeLock: {
|
|
586
|
-
reason: "
|
|
586
|
+
reason: "Puede verlo, pero no modificarlo.",
|
|
587
587
|
},
|
|
588
588
|
accountState: {
|
|
589
589
|
// Agreeing with "cuenta" (feminine): the chip is the state of an account.
|
|
@@ -601,7 +601,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
601
601
|
emailOptional: "Correo electrónico (opcional)",
|
|
602
602
|
// example.com is reserved for examples (RFC 2606); a localised domain is real.
|
|
603
603
|
emailPlaceholder: "nombre@example.com",
|
|
604
|
-
invalidEmail: "
|
|
604
|
+
invalidEmail: "Introduzca una dirección de correo completa.",
|
|
605
605
|
role: "Rol",
|
|
606
606
|
roleOf: (name) => `Rol de ${name}`,
|
|
607
607
|
add: "Compartir",
|
|
@@ -616,12 +616,12 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
616
616
|
removeConfirm: (name) => `¿Quitar el acceso a ${name}?`,
|
|
617
617
|
revokePending: "Retirar invitación",
|
|
618
618
|
revokePendingConfirm: (name) => `¿Retirar la invitación para ${name}?`,
|
|
619
|
-
failed: "No ha funcionado.
|
|
619
|
+
failed: "No ha funcionado. Inténtelo de nuevo.",
|
|
620
620
|
loading: "Cargando…",
|
|
621
621
|
},
|
|
622
622
|
reauthDialog: {
|
|
623
|
-
title: "
|
|
624
|
-
description: "
|
|
623
|
+
title: "Confirme que es usted",
|
|
624
|
+
description: "Introduzca su contraseña actual para continuar.",
|
|
625
625
|
password: "Contraseña actual",
|
|
626
626
|
submit: "Continuar",
|
|
627
627
|
cancel: "Cancelar",
|
|
@@ -631,6 +631,57 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
631
631
|
waking: (appName) =>
|
|
632
632
|
`El servidor se duerme cuando nadie usa ${appName ?? "la aplicación"}, así que la primera solicitud tras una pausa tiene que volver a arrancarlo. Puede tardar un momento; no se pierde nada y la página se completará sola.`,
|
|
633
633
|
},
|
|
634
|
+
legal: {
|
|
635
|
+
navLabel: "Información legal",
|
|
636
|
+
},
|
|
637
|
+
translationReview: {
|
|
638
|
+
statusMissing: "Falta",
|
|
639
|
+
statusUnreviewed: "Sin revisar",
|
|
640
|
+
statusChanged: "Cambiado desde la revisión",
|
|
641
|
+
statusNeedsChange: "Requiere cambios",
|
|
642
|
+
statusApproved: "Aprobado",
|
|
643
|
+
statusFilter: "Estado",
|
|
644
|
+
all: "Todos",
|
|
645
|
+
filterCount: (label, count) => `${label} · ${n(count)}`,
|
|
646
|
+
source: "Textos",
|
|
647
|
+
allSources: "Todos los textos",
|
|
648
|
+
namespace: "Área",
|
|
649
|
+
allNamespaces: "Todas las áreas",
|
|
650
|
+
search: "Buscar en claves y textos",
|
|
651
|
+
placeholdersOnly: (count) => `Solo problemas de marcadores (${n(count)})`,
|
|
652
|
+
placeholderChip: "Marcadores",
|
|
653
|
+
key: "Clave",
|
|
654
|
+
statusColumn: "Estado",
|
|
655
|
+
empty: "Ningún texto coincide con estos filtros.",
|
|
656
|
+
missingText: "Falta en este idioma",
|
|
657
|
+
progress: (approved, total) => `${n(approved)} de ${n(total)} ${plural(approved, "aprobado", "aprobados")}`,
|
|
658
|
+
localeProgress: (approved, total) => `${n(approved)}/${n(total)}`,
|
|
659
|
+
locales: "Idiomas",
|
|
660
|
+
approve: "Aprobar",
|
|
661
|
+
flag: "Requiere cambios",
|
|
662
|
+
suggest: "Proponer traducción",
|
|
663
|
+
reset: "Marcar como sin revisar",
|
|
664
|
+
cancel: "Cancelar",
|
|
665
|
+
approveSelected: "Aprobar la selección",
|
|
666
|
+
resetSelected: "Marcar la selección como sin revisar",
|
|
667
|
+
selectShown: "Seleccionar todo lo mostrado",
|
|
668
|
+
changedSince: "Este texto cambió después de revisarse. Vuelva a leerlo, por favor.",
|
|
669
|
+
reviewedWording: "Redacción al revisarse",
|
|
670
|
+
reviewedReference: "Referencia al revisarse",
|
|
671
|
+
placeholderMismatch: (reference, text) =>
|
|
672
|
+
`Los marcadores no coinciden con la referencia — referencia: ${reference}; este texto: ${text}. La aplicación los rellena, así que deben quedar exactamente como están.`,
|
|
673
|
+
lastApproved: (name, date) => `Aprobado por ${name} el ${date}`,
|
|
674
|
+
lastFlagged: (name, date) => `Marcado para cambiar por ${name} el ${date}`,
|
|
675
|
+
erasedReviewer: "una cuenta eliminada",
|
|
676
|
+
suggestion: "Mejor redacción",
|
|
677
|
+
translation: "Traducción",
|
|
678
|
+
note: "Nota (opcional)",
|
|
679
|
+
notePlaceholder: "Qué está mal o qué hay que tener en cuenta",
|
|
680
|
+
readOnly: "Puede leer este idioma, pero no revisarlo.",
|
|
681
|
+
scope: (areas) => `Su revisión se limita a: ${areas}.`,
|
|
682
|
+
exportCorrections: (count) => `Exportar correcciones (${n(count)})`,
|
|
683
|
+
failed: "No ha funcionado. Inténtelo de nuevo.",
|
|
684
|
+
},
|
|
634
685
|
};
|
|
635
686
|
}
|
|
636
687
|
|