poe-svelte-ui-lib 1.45.0 → 1.47.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 +2 -0
- package/dist/Button/ButtonProps.svelte +5 -4
- package/dist/Input/InputProps.svelte +3 -2
- package/dist/TextsEditor.svelte +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/locales/i18n.d.ts +7 -3
- package/dist/locales/i18n.js +47 -30
- package/dist/locales/languages.d.ts +23 -0
- package/dist/locales/languages.js +32 -0
- package/dist/locales/localize.d.ts +1 -2
- package/dist/locales/localize.js +11 -6
- package/dist/locales/translations.d.ts +2 -2
- package/dist/locales/translations.js +815 -759
- package/package.json +1 -1
- package/dist/locales/FlagEn.svelte +0 -16
- package/dist/locales/FlagEn.svelte.d.ts +0 -18
- package/dist/locales/FlagRu.svelte +0 -10
- package/dist/locales/FlagRu.svelte.d.ts +0 -18
- package/dist/locales/FlagZh.svelte +0 -10
- package/dist/locales/FlagZh.svelte.d.ts +0 -18
package/README.md
CHANGED
|
@@ -110,6 +110,8 @@ npm install poe-svelte-ui-lib
|
|
|
110
110
|
|
|
111
111
|
**README компонента** (`src/lib/<Компонент>/README.md`, вкладка «Инфо» витрины) — развёрнутая документация для разработчика. Первые разделы повторяют описание (i) и не должны ему противоречить, дальше идут разработческие: `## Назначение` (плюс заметка о размерах) → `## Пакет данных` (плюс `### Протокол устройства` / пример кода прошивки) → `## Ключи` (таблица «Ключ / Описание», те же формулировки, что в (i)) → `## Особенности` (поведение, ограничения) → `## Пропсы` (таблица «Название / Тип / По умолчанию / Описание», колбэки — `### Колбэки`) → `## Примеры` (Svelte) → `## Конструктор свойств (<Компонент>Props.svelte)` → `## Внутреннее устройство`.
|
|
112
112
|
|
|
113
|
+
**Языки — единственный источник в библиотеке.** Список языков — `LANGUAGES` в `locales/languages.ts`: код, название и флаг (SVG-строка), без Svelte — годится и для серверного кода. **Новый язык — одна запись в `LANGUAGES`**: переключатели языка во всех приложениях (`LOCALES`), колонки окна «Тексты и переводы», многоязычные тексты GUI и словари подхватывают его сами; пока перевода нет — показывается текст языка по умолчанию (`DEFAULT_LANGUAGE`, `ru`). Словари (`Translations`) — строки-объекты по кодам `{ ru: "…", en: "…", zh: "…" }`: обязателен только язык по умолчанию, порядок не важен; недостающие переводы показывает `findMissingTranslations(словарь)`. `locales/i18n.ts` — текущий язык `Language`/`setLanguage` (неизвестный код не применяется, выбор сохраняется в `localStorage` `AppLanguage`), `restoreLanguage()` (сохранённый или язык браузера; стартовое значение всегда язык по умолчанию — серверный рендер совпадает с гидратацией, поэтому SSR-приложения вызывают его в `onMount` корневого layout, SPA — при старте), `LOCALES` (с флагом `flag`) и механизм перевода `createTranslator(словарь)`. Приложения (DevCloud, WebApp, терминал) свой список языков, хранилище и функцию перевода не держат: берут их из библиотеки и строят свой `$T` из своего словаря — `export const T = createTranslator(translations)`. Одно `setLanguage` переключает и компоненты библиотеки, и приложение. Многоязычные тексты GUI распознаются по виду ключей (коды языков), поэтому данные с языком, убранным из списка, не ломаются.
|
|
114
|
+
|
|
113
115
|
**Языки (ru/en/zh) — два слоя:**
|
|
114
116
|
|
|
115
117
|
- **Встроенные тексты компонентов** (подписи полей виджетов, кнопки, подсказки, `aria-label`) — только через словарь `src/lib/locales/translations.ts` и `$T("ключ")`; строка с числами — параметрами: `$T("widget.fs.files", { n: files.length })` на ключ `"Файлов: {n}"`. Русских литералов в разметке и скриптах компонентов быть не должно. Списки вариантов с подписями — `$derived`, иначе не переключатся при смене языка. Исключение — шкалы прибора `WidgetPFD`: там общепринятые авиационные сокращения (`SPD`, `ALT`, `AS`, `VS`, `NO DATA`) на всех языках, переводятся только подсказки и строки состояния.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import TextsEditor from "../TextsEditor.svelte"
|
|
4
4
|
import { localizeText } from "../locales/localize"
|
|
5
|
+
import { DEFAULT_LANGUAGE } from "../locales/languages"
|
|
5
6
|
import { T } from "../locales/i18n"
|
|
6
7
|
import { type UIComponent, type IButtonProps, type IOption, updateProperty, type IUIComponentHandler } from "../types"
|
|
7
8
|
import * as UI from ".."
|
|
@@ -42,11 +43,11 @@
|
|
|
42
43
|
)
|
|
43
44
|
const updateIconProperty = (propertyName: string, value: string) => {
|
|
44
45
|
updateProperty(propertyName, value, component, onPropertyChange)
|
|
45
|
-
if (component.properties.content.icon && !localizeText(component.properties.content.name,
|
|
46
|
+
if (component.properties.content.icon && !localizeText(component.properties.content.name, DEFAULT_LANGUAGE).trim())
|
|
46
47
|
updateProperty("componentClass", twMerge((component.properties as UI.IButtonProps).componentClass, "w-fit p-2"), component, onPropertyChange)
|
|
47
48
|
else if (
|
|
48
|
-
(component.properties.content.icon && localizeText(component.properties.content.name,
|
|
49
|
-
(!component.properties.content.icon && !localizeText(component.properties.content.name,
|
|
49
|
+
(component.properties.content.icon && localizeText(component.properties.content.name, DEFAULT_LANGUAGE).trim()) ||
|
|
50
|
+
(!component.properties.content.icon && !localizeText(component.properties.content.name, DEFAULT_LANGUAGE).trim())
|
|
50
51
|
)
|
|
51
52
|
updateProperty("componentClass", twMerge((component.properties as UI.IButtonProps).componentClass, "w-full px-1 py-1"), component, onPropertyChange)
|
|
52
53
|
}
|
|
@@ -106,7 +107,7 @@
|
|
|
106
107
|
{/snippet}
|
|
107
108
|
|
|
108
109
|
{#snippet ButtonHeight()}
|
|
109
|
-
{#if !(!localizeText(component.properties.content.name,
|
|
110
|
+
{#if !(!localizeText(component.properties.content.name, DEFAULT_LANGUAGE).trim() && component.properties.content.icon)}
|
|
110
111
|
<UI.Select
|
|
111
112
|
label={{ name: $T("constructor.props.height") }}
|
|
112
113
|
type="buttons"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { getContext } from "svelte"
|
|
4
4
|
import { T } from "../locales/i18n"
|
|
5
5
|
import translations from "../locales/translations"
|
|
6
|
-
import { LANGUAGE_CODES } from "../locales/
|
|
6
|
+
import { DEFAULT_LANGUAGE, LANGUAGE_CODES } from "../locales/languages"
|
|
7
7
|
import type { IInputProps, UIComponent, IOption, IUIComponentHandler } from "../types"
|
|
8
8
|
import * as UI from ".."
|
|
9
9
|
import { optionsStore, findColorOption } from "../options"
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
/* Подсказка битового режима «Максимальное число: N» сразу на всех языках — иначе записалась бы на языке интерфейса конструктора */
|
|
40
40
|
const maxNumInfo = (range: { start: number; end: number }) => {
|
|
41
41
|
const max = Math.pow(2, range.end - range.start + 1) - 1
|
|
42
|
-
|
|
42
|
+
const row = translations["constructor.props.maxnum"]
|
|
43
|
+
return Object.fromEntries(LANGUAGE_CODES.map((lang) => [lang, `${row[lang] || row[DEFAULT_LANGUAGE]}: ${max}`]))
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
const updateProperty = (path: string, value: string | object | boolean | number | RegExp) => {
|
package/dist/TextsEditor.svelte
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
поэтому окно одно на все компоненты; окна свойств сами тексты не показывают и не правят. -->
|
|
4
4
|
<script lang="ts">
|
|
5
5
|
import { T } from "./locales/i18n"
|
|
6
|
-
import {
|
|
7
|
-
import type
|
|
6
|
+
import { collectTexts, packLocalized, setAtPath, type TextPath } from "./locales/localize"
|
|
7
|
+
import { DEFAULT_LANGUAGE, LANGUAGE_CODES, type LanguageCode } from "./locales/languages"
|
|
8
8
|
import Button from "./Button/Button.svelte"
|
|
9
9
|
import Input from "./Input/Input.svelte"
|
|
10
10
|
import Modal from "./Modal.svelte"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
const skip = NOT_TEXTS[component.type] ?? []
|
|
44
44
|
rows = collectTexts(component.properties)
|
|
45
45
|
.filter(({ path }) => !skip.includes(String(path[0])))
|
|
46
|
-
.map(({ path, text }) => ({ path, values: typeof text === "string" ? {
|
|
46
|
+
.map(({ path, text }) => ({ path, values: typeof text === "string" ? { [DEFAULT_LANGUAGE]: text } : { ...text } }))
|
|
47
47
|
isOpen = true
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
{#each LANGUAGE_CODES as lang (lang)}
|
|
70
70
|
<Input
|
|
71
71
|
value={row.values[lang] ?? ""}
|
|
72
|
-
placeholder={lang ===
|
|
72
|
+
placeholder={lang === DEFAULT_LANGUAGE ? "" : row.values[DEFAULT_LANGUAGE]}
|
|
73
73
|
onUpdate={(value) => (rows[index].values[lang] = String(value))}
|
|
74
74
|
/>
|
|
75
75
|
{/each}
|
package/dist/index.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export { default as WidgetPFDProps } from "./WidgetPFD/WidgetPFDProps.svelte";
|
|
|
51
51
|
export { default as WidgetMap } from "./WidgetMap/WidgetMap.svelte";
|
|
52
52
|
export { default as WidgetMapProps } from "./WidgetMap/WidgetMapProps.svelte";
|
|
53
53
|
export * from "./locales/i18n";
|
|
54
|
-
export * from "./locales/
|
|
54
|
+
export * from "./locales/languages";
|
|
55
55
|
export * from "./locales/localize";
|
|
56
56
|
export { widgetAccentStyle } from "./widgetAccent";
|
|
57
57
|
export * from "./ModalStackStore";
|
package/dist/index.js
CHANGED
|
@@ -52,7 +52,7 @@ export { default as WidgetPFDProps } from "./WidgetPFD/WidgetPFDProps.svelte";
|
|
|
52
52
|
export { default as WidgetMap } from "./WidgetMap/WidgetMap.svelte";
|
|
53
53
|
export { default as WidgetMapProps } from "./WidgetMap/WidgetMapProps.svelte";
|
|
54
54
|
export * from "./locales/i18n";
|
|
55
|
-
export * from "./locales/
|
|
55
|
+
export * from "./locales/languages";
|
|
56
56
|
export * from "./locales/localize";
|
|
57
57
|
export { widgetAccentStyle } from "./widgetAccent";
|
|
58
58
|
export * from "./ModalStackStore";
|
package/dist/locales/i18n.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { type Readable } from "svelte/store";
|
|
2
|
+
import { type LanguageCode, type Translations } from "./languages";
|
|
2
3
|
export declare const Language: import("svelte/store").Writable<string>;
|
|
4
|
+
export declare function restoreLanguage(): void;
|
|
3
5
|
export declare function setLanguage(newLang: string): void;
|
|
4
6
|
export declare const LOCALES: {
|
|
5
7
|
id: string;
|
|
6
8
|
name: string;
|
|
7
|
-
value:
|
|
8
|
-
|
|
9
|
+
value: LanguageCode;
|
|
10
|
+
flag: string;
|
|
9
11
|
}[];
|
|
10
12
|
export type TranslateParams = Record<string, string | number>;
|
|
11
|
-
export
|
|
13
|
+
export type Translator = (key: string, params?: TranslateParams) => string;
|
|
14
|
+
export declare const createTranslator: (dictionary: Translations) => Readable<Translator>;
|
|
15
|
+
export declare const T: Readable<Translator>;
|
package/dist/locales/i18n.js
CHANGED
|
@@ -1,39 +1,56 @@
|
|
|
1
|
-
import { derived } from "svelte/store";
|
|
2
|
-
import { writable } from "svelte/store";
|
|
1
|
+
import { derived, writable } from "svelte/store";
|
|
3
2
|
import translations from "./translations";
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
import { DEFAULT_LANGUAGE, LANGUAGES, isLanguageCode } from "./languages";
|
|
4
|
+
/* Язык и механизм перевода — общие для библиотеки и приложений: приложение берёт отсюда Language/setLanguage/LOCALES и строит свой $T
|
|
5
|
+
из своего словаря через createTranslator (см. README «Языки»); переключение языка одним setLanguage меняет и компоненты, и приложение */
|
|
6
|
+
/*
|
|
7
|
+
Текущий язык. Стартует всегда с языка по умолчанию — серверный рендер и гидратация совпадают (Svelte 5 не перерисовывает текст при
|
|
8
|
+
гидратации); сохранённый выбор применяет restoreLanguage(): в SSR-приложениях — в onMount корневого layout, в SPA — при старте
|
|
9
|
+
*/
|
|
10
|
+
export const Language = writable(DEFAULT_LANGUAGE);
|
|
11
|
+
/* Сохранённый язык → язык браузера; неизвестный код не применяется */
|
|
12
|
+
export function restoreLanguage() {
|
|
13
|
+
if (typeof window === "undefined")
|
|
14
|
+
return;
|
|
15
|
+
let saved = null;
|
|
16
|
+
try {
|
|
17
|
+
saved = localStorage.getItem("AppLanguage");
|
|
15
18
|
}
|
|
19
|
+
catch {
|
|
20
|
+
/* хранилище недоступно */
|
|
21
|
+
}
|
|
22
|
+
const candidate = saved || navigator.language?.split("-")[0]?.toLowerCase();
|
|
23
|
+
if (isLanguageCode(candidate))
|
|
24
|
+
setLanguage(candidate);
|
|
16
25
|
}
|
|
17
|
-
/*
|
|
18
|
-
export const Language = writable(initialLanguage);
|
|
19
|
-
/* Функция для изменения языка */
|
|
26
|
+
/* Смена языка с сохранением выбора */
|
|
20
27
|
export function setLanguage(newLang) {
|
|
28
|
+
if (!isLanguageCode(newLang))
|
|
29
|
+
return;
|
|
21
30
|
if (typeof window !== "undefined") {
|
|
22
|
-
|
|
31
|
+
try {
|
|
32
|
+
localStorage.setItem("AppLanguage", newLang);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
/* хранилище недоступно — выбор действует до перезагрузки */
|
|
36
|
+
}
|
|
23
37
|
}
|
|
24
38
|
Language.set(newLang);
|
|
25
39
|
}
|
|
26
|
-
/*
|
|
27
|
-
export const LOCALES =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const text =
|
|
40
|
+
/* Языки для переключателей: список из languages.ts; flag — SVG-разметка (иконка кнопки или {@html}) */
|
|
41
|
+
export const LOCALES = LANGUAGES.map((language) => ({
|
|
42
|
+
id: `app-language-${language.value}`,
|
|
43
|
+
name: language.name,
|
|
44
|
+
value: language.value,
|
|
45
|
+
flag: language.flag,
|
|
46
|
+
}));
|
|
47
|
+
/* Перевод по словарю: текущий язык → язык по умолчанию → сам ключ */
|
|
48
|
+
const translate = (dictionary, locale, key, params) => {
|
|
49
|
+
const row = dictionary[key];
|
|
50
|
+
const text = (row && (row[locale] || row[DEFAULT_LANGUAGE])) || key;
|
|
37
51
|
return params ? text.replace(/\{(\w+)\}/g, (match, name) => (name in params ? String(params[name]) : match)) : text;
|
|
38
|
-
}
|
|
39
|
-
|
|
52
|
+
};
|
|
53
|
+
/* $T приложения из его собственного словаря — поверх общего языка */
|
|
54
|
+
export const createTranslator = (dictionary) => derived(Language, ($lang) => (key, params) => translate(dictionary, $lang, key, params));
|
|
55
|
+
/* $T самой библиотеки */
|
|
56
|
+
export const T = createTranslator(translations);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const LANGUAGES: readonly [{
|
|
2
|
+
readonly value: "ru";
|
|
3
|
+
readonly name: "Русский";
|
|
4
|
+
readonly flag: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 512 512\" ><mask id=\"circleFlagsRu0\"><circle cx=\"256\" cy=\"256\" r=\"256\" fill=\"#fff\" /></mask><g mask=\"url(#circleFlagsRu0)\" ><path fill=\"#0052b4\" d=\"M512 170v172l-256 32L0 342V170l256-32z\" /><path fill=\"#eee\" d=\"M512 0v170H0V0Z\" /><path fill=\"#d80027\" d=\"M512 342v170H0V342Z\" /></g ></svg >";
|
|
5
|
+
}, {
|
|
6
|
+
readonly value: "en";
|
|
7
|
+
readonly name: "English";
|
|
8
|
+
readonly flag: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 512 512\" ><clipPath id=\"svgIDa\"><circle cx=\"256\" cy=\"256\" r=\"256\" /></clipPath><g clip-path=\"url(#svgIDa)\" ><path fill=\"#eee\" d=\"m0 0l8 16l-8 15v16l32 65l-32 64v32l32 48l-32 48v32l32 64l-32 65v47l16-8l15 8h16l65-32l64 32h32l48-32l48 32h32l64-32l65 32h47l-8-15l8-16v-16l-32-65l32-64v-32l-32-48l32-48v-32l-32-64l32-65V0l-15 8l-16-8h-16l-65 32l-64-32h-32l-48 32l-48-32h-32l-64 32L47 0H0z\" /><path fill=\"#0052b4\" d=\"m47 0l129 129V0Zm289 0v129L465 0ZM0 47v129h129Zm512 0L383 176h129ZM0 336v129l129-129Zm383 0l129 129V336Zm-47 47v129h129zm-160 0L47 512h129Z\" /><path fill=\"#d80027\" d=\"M208 0v208H0v96h208v208h96V304h208v-96H304V0h-96z\" /><path fill=\"#d80027\" d=\"m336 336l176 176v-31L367 336Zm0-160L512 0h-31L336 145Zm-160 0L0 0v31l145 145zm0 160L0 512h31l145-145Z\" /></g ></svg >";
|
|
9
|
+
}, {
|
|
10
|
+
readonly value: "zh";
|
|
11
|
+
readonly name: "中文";
|
|
12
|
+
readonly flag: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 512 512\" ><mask id=\"circleFlagsCn0\"><circle cx=\"256\" cy=\"256\" r=\"256\" fill=\"#fff\" /></mask><g mask=\"url(#circleFlagsCn0)\" ><path fill=\"#d80027\" d=\"M0 0h512v512H0z\" /><path fill=\"#ffda44\" d=\"m140.1 155.8l22.1 68h71.5l-57.8 42.1l22.1 68l-57.9-42l-57.9 42l22.2-68l-57.9-42.1H118zm163.4 240.7l-16.9-20.8l-25 9.7l14.5-22.5l-16.9-20.9l25.9 6.9l14.6-22.5l1.4 26.8l26 6.9l-25.1 9.6zm33.6-61l8-25.6l-21.9-15.5l26.8-.4l7.9-25.6l8.7 25.4l26.8-.3l-21.5 16l8.6 25.4l-21.9-15.5zm45.3-147.6L370.6 212l19.2 18.7l-26.5-3.8l-11.8 24l-4.6-26.4l-26.6-3.8l23.8-12.5l-4.6-26.5l19.2 18.7zm-78.2-73l-2 26.7l24.9 10.1l-26.1 6.4l-1.9 26.8l-14.1-22.8l-26.1 6.4l17.3-20.5l-14.2-22.7l24.9 10.1z\" /></g ></svg >";
|
|
13
|
+
}];
|
|
14
|
+
export type LanguageCode = (typeof LANGUAGES)[number]["value"];
|
|
15
|
+
export declare const DEFAULT_LANGUAGE = "ru";
|
|
16
|
+
export declare const LANGUAGE_CODES: LanguageCode[];
|
|
17
|
+
export type Translation = Record<typeof DEFAULT_LANGUAGE, string> & Partial<Record<LanguageCode, string>>;
|
|
18
|
+
export type Translations = Record<string, Translation>;
|
|
19
|
+
export declare const isLanguageCode: (value: unknown) => value is LanguageCode;
|
|
20
|
+
export declare const findMissingTranslations: (dictionary: Translations) => {
|
|
21
|
+
key: string;
|
|
22
|
+
languages: LanguageCode[];
|
|
23
|
+
}[];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* $lib/locales/languages.ts */
|
|
2
|
+
/*
|
|
3
|
+
Единственный источник списка языков для библиотеки и всех приложений на ней (DevCloud, WebApp, терминал); без Svelte — годится и для
|
|
4
|
+
серверного кода. Новый язык — одна запись здесь (код, название, флаг SVG): переключатели языка, окно «Тексты и переводы» и многоязычные
|
|
5
|
+
тексты GUI подхватят его сами, а пока в словарях нет перевода — показывается текст языка по умолчанию.
|
|
6
|
+
*/
|
|
7
|
+
export const LANGUAGES = [
|
|
8
|
+
{
|
|
9
|
+
value: "ru",
|
|
10
|
+
name: "Русский",
|
|
11
|
+
flag: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512" ><mask id="circleFlagsRu0"><circle cx="256" cy="256" r="256" fill="#fff" /></mask><g mask="url(#circleFlagsRu0)" ><path fill="#0052b4" d="M512 170v172l-256 32L0 342V170l256-32z" /><path fill="#eee" d="M512 0v170H0V0Z" /><path fill="#d80027" d="M512 342v170H0V342Z" /></g ></svg >`,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
value: "en",
|
|
15
|
+
name: "English",
|
|
16
|
+
flag: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512" ><clipPath id="svgIDa"><circle cx="256" cy="256" r="256" /></clipPath><g clip-path="url(#svgIDa)" ><path fill="#eee" d="m0 0l8 16l-8 15v16l32 65l-32 64v32l32 48l-32 48v32l32 64l-32 65v47l16-8l15 8h16l65-32l64 32h32l48-32l48 32h32l64-32l65 32h47l-8-15l8-16v-16l-32-65l32-64v-32l-32-48l32-48v-32l-32-64l32-65V0l-15 8l-16-8h-16l-65 32l-64-32h-32l-48 32l-48-32h-32l-64 32L47 0H0z" /><path fill="#0052b4" d="m47 0l129 129V0Zm289 0v129L465 0ZM0 47v129h129Zm512 0L383 176h129ZM0 336v129l129-129Zm383 0l129 129V336Zm-47 47v129h129zm-160 0L47 512h129Z" /><path fill="#d80027" d="M208 0v208H0v96h208v208h96V304h208v-96H304V0h-96z" /><path fill="#d80027" d="m336 336l176 176v-31L367 336Zm0-160L512 0h-31L336 145Zm-160 0L0 0v31l145 145zm0 160L0 512h31l145-145Z" /></g ></svg >`,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: "zh",
|
|
20
|
+
name: "中文",
|
|
21
|
+
flag: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512" ><mask id="circleFlagsCn0"><circle cx="256" cy="256" r="256" fill="#fff" /></mask><g mask="url(#circleFlagsCn0)" ><path fill="#d80027" d="M0 0h512v512H0z" /><path fill="#ffda44" d="m140.1 155.8l22.1 68h71.5l-57.8 42.1l22.1 68l-57.9-42l-57.9 42l22.2-68l-57.9-42.1H118zm163.4 240.7l-16.9-20.8l-25 9.7l14.5-22.5l-16.9-20.9l25.9 6.9l14.6-22.5l1.4 26.8l26 6.9l-25.1 9.6zm33.6-61l8-25.6l-21.9-15.5l26.8-.4l7.9-25.6l8.7 25.4l26.8-.3l-21.5 16l8.6 25.4l-21.9-15.5zm45.3-147.6L370.6 212l19.2 18.7l-26.5-3.8l-11.8 24l-4.6-26.4l-26.6-3.8l23.8-12.5l-4.6-26.5l19.2 18.7zm-78.2-73l-2 26.7l24.9 10.1l-26.1 6.4l-1.9 26.8l-14.1-22.8l-26.1 6.4l17.3-20.5l-14.2-22.7l24.9 10.1z" /></g ></svg >`,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
/* Язык по умолчанию: обязателен в каждой строке словаря и в многоязычных текстах, им заменяется отсутствующий перевод */
|
|
25
|
+
export const DEFAULT_LANGUAGE = "ru";
|
|
26
|
+
/* Коды в порядке LANGUAGES — порядок показа в переключателях и колонках переводов */
|
|
27
|
+
export const LANGUAGE_CODES = LANGUAGES.map((language) => language.value);
|
|
28
|
+
export const isLanguageCode = (value) => LANGUAGE_CODES.includes(value);
|
|
29
|
+
/* Ключи словаря без перевода на какой-либо язык — для проверки полноты словаря при добавлении языка */
|
|
30
|
+
export const findMissingTranslations = (dictionary) => Object.entries(dictionary)
|
|
31
|
+
.map(([key, row]) => ({ key, languages: LANGUAGE_CODES.filter((code) => !row[code]) }))
|
|
32
|
+
.filter((entry) => entry.languages.length > 0);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type LanguageCode } from "./languages";
|
|
2
2
|
export type LocalizedText = string | Partial<Record<LanguageCode, string>>;
|
|
3
|
-
export declare const LANGUAGE_CODES: LanguageCode[];
|
|
4
3
|
export declare const isLocalizedText: (value: unknown) => value is Partial<Record<LanguageCode, string>>;
|
|
5
4
|
export declare const localizeText: (value: LocalizedText | undefined, lang: string) => string;
|
|
6
5
|
export declare function localizeDeep<T>(value: T, lang: string): T;
|
package/dist/locales/localize.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/* $lib/locales/localize.ts */
|
|
2
|
+
/* Многоязычные тексты, введённые в конструкторе: строка (язык по умолчанию) или объект по кодам языков { ru, en, zh, ... };
|
|
3
|
+
язык по умолчанию (DEFAULT_LANGUAGE) — основной и запасной; набор и порядок языков — LANGUAGES (languages.ts) */
|
|
4
|
+
import { DEFAULT_LANGUAGE, LANGUAGE_CODES } from "./languages";
|
|
5
|
+
/* Ключ многоязычного текста: код языка из LANGUAGES или похожий на код (ru, en, pt-BR) — данные с языком, убранным из списка, не ломаются */
|
|
6
|
+
const LANGUAGE_KEY = /^[a-z]{2,3}(-[A-Z]{2})?$/;
|
|
2
7
|
/* Поля свойств, которые считаются текстом для пользователя — их показывает и правит окно «Тексты и переводы» (TextsEditor) */
|
|
3
8
|
const TEXT_KEYS = new Set([
|
|
4
9
|
"name",
|
|
@@ -21,13 +26,13 @@ export const isLocalizedText = (value) => {
|
|
|
21
26
|
if (!isPlainObject(value))
|
|
22
27
|
return false;
|
|
23
28
|
const keys = Object.keys(value);
|
|
24
|
-
return keys.length > 0 && keys.every((key) =>
|
|
29
|
+
return keys.length > 0 && keys.every((key) => LANGUAGE_KEY.test(key) && typeof value[key] === "string");
|
|
25
30
|
};
|
|
26
|
-
/* Текст на нужном языке: сам язык →
|
|
31
|
+
/* Текст на нужном языке: сам язык → язык по умолчанию → первый непустой */
|
|
27
32
|
export const localizeText = (value, lang) => {
|
|
28
33
|
if (value === undefined || typeof value === "string")
|
|
29
34
|
return value ?? "";
|
|
30
|
-
return value[lang] || value
|
|
35
|
+
return value[lang] || value[DEFAULT_LANGUAGE] || Object.values(value).find(Boolean) || "";
|
|
31
36
|
};
|
|
32
37
|
/* Копия структуры, где каждый многоязычный текст заменён строкой на языке lang; функции, компоненты и бинарные данные — как есть */
|
|
33
38
|
export function localizeDeep(value, lang) {
|
|
@@ -60,11 +65,11 @@ export function setAtPath(target, path, value) {
|
|
|
60
65
|
copy[head] = setAtPath(copy[head], rest, value);
|
|
61
66
|
return copy;
|
|
62
67
|
}
|
|
63
|
-
/* Текст из
|
|
68
|
+
/* Текст из полей окна переводов (по полю на язык): пусто — "", только язык по умолчанию — строка, иначе объект из непустых языков */
|
|
64
69
|
export const packLocalized = (values) => {
|
|
65
70
|
const filled = Object.fromEntries(LANGUAGE_CODES.filter((lang) => values[lang]?.trim()).map((lang) => [lang, values[lang].trim()]));
|
|
66
71
|
const langs = Object.keys(filled);
|
|
67
72
|
if (!langs.length)
|
|
68
73
|
return "";
|
|
69
|
-
return langs.length === 1 && langs[0] ===
|
|
74
|
+
return langs.length === 1 && langs[0] === DEFAULT_LANGUAGE ? filled[DEFAULT_LANGUAGE] : filled;
|
|
70
75
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
declare const translations:
|
|
1
|
+
import type { Translations } from "./languages";
|
|
2
|
+
declare const translations: Translations;
|
|
3
3
|
export default translations;
|