@reformer/core 11.0.0-beta.5 → 11.0.0-beta.7

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.
Files changed (95) hide show
  1. package/dist/behaviors.js +124 -124
  2. package/dist/create-model-B8U-COv-.js +103 -0
  3. package/dist/en-CjAqn7h0.js +41 -0
  4. package/dist/form/behaviors/internals.d.ts +9 -0
  5. package/dist/form/create-form.d.ts +6 -6
  6. package/dist/form/factories/node-factory.d.ts +16 -16
  7. package/dist/form/nodes/array-node.d.ts +3 -3
  8. package/dist/form/nodes/field-node.d.ts +3 -3
  9. package/dist/form/nodes/group-node.d.ts +7 -7
  10. package/dist/form/nodes/model-array-node.d.ts +2 -2
  11. package/dist/form/types/contracts.d.ts +6 -0
  12. package/dist/form/types/deep-schema.d.ts +5 -5
  13. package/dist/form/types/validation-schema.d.ts +13 -1
  14. package/dist/form/validation/operators.d.ts +6 -1
  15. package/dist/form/validators/validation-error.d.ts +8 -0
  16. package/dist/i18n/descriptor.d.ts +54 -0
  17. package/dist/i18n/en.json.d.ts +45 -0
  18. package/dist/i18n/loader.d.ts +71 -0
  19. package/dist/i18n/locale-store.d.ts +39 -0
  20. package/dist/i18n/locale.d.ts +90 -0
  21. package/dist/i18n/message-format.d.ts +116 -0
  22. package/dist/i18n/resolve-localized.d.ts +17 -0
  23. package/dist/i18n/ru.json.d.ts +45 -0
  24. package/dist/i18n/translator.d.ts +50 -0
  25. package/dist/i18n/validation-message.d.ts +18 -0
  26. package/dist/i18n.d.ts +31 -0
  27. package/dist/i18n.js +303 -0
  28. package/dist/index-BrNkQj1y.js +105 -0
  29. package/dist/index.js +548 -601
  30. package/dist/loader-Dv_Hl7JC.js +216 -0
  31. package/dist/locale/en.d.ts +2 -0
  32. package/dist/locale/en.js +5 -0
  33. package/dist/locale/en.json +42 -0
  34. package/dist/locale/index.d.ts +18 -0
  35. package/dist/locale/ru.d.ts +2 -0
  36. package/dist/locale/ru.js +41 -0
  37. package/dist/locale/ru.json +42 -0
  38. package/dist/locale.d.ts +2 -0
  39. package/dist/locale.js +9 -0
  40. package/dist/model/create-model.d.ts +17 -3
  41. package/dist/model/index.d.ts +3 -3
  42. package/dist/model/model-signals-proxy.d.ts +25 -0
  43. package/dist/model/types.d.ts +19 -5
  44. package/dist/model-signals-proxy-2vqS243J.js +250 -0
  45. package/dist/model.js +18 -16
  46. package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
  47. package/dist/platforms/react/field-binding/field-handle.d.ts +50 -0
  48. package/dist/platforms/react/hooks/types.d.ts +6 -3
  49. package/dist/platforms/react/hooks/useFormControlValue.d.ts +2 -1
  50. package/dist/platforms/react/i18n/context.d.ts +13 -0
  51. package/dist/platforms/react/i18n/hooks.d.ts +59 -0
  52. package/dist/platforms/react/i18n/provider.d.ts +52 -0
  53. package/dist/platforms/react/index.d.ts +4 -0
  54. package/dist/{strategy-DA3JwxsR.js → strategy-BXS_gBfz.js} +7 -7
  55. package/dist/validation-error-B-U3RsV0.js +11 -0
  56. package/dist/validation.js +1 -1
  57. package/dist/validators/email.js +5 -8
  58. package/dist/validators/future-date.js +8 -11
  59. package/dist/validators/integer.js +4 -7
  60. package/dist/validators/is-date.js +5 -8
  61. package/dist/validators/is-number.js +4 -7
  62. package/dist/validators/max-age.js +8 -11
  63. package/dist/validators/max-date.js +8 -11
  64. package/dist/validators/max-length.js +5 -8
  65. package/dist/validators/max.js +4 -7
  66. package/dist/validators/min-age.js +8 -11
  67. package/dist/validators/min-date.js +8 -11
  68. package/dist/validators/min-length.js +5 -8
  69. package/dist/validators/min.js +4 -7
  70. package/dist/validators/multiple-of.js +5 -8
  71. package/dist/validators/non-negative.js +4 -7
  72. package/dist/validators/non-zero.js +4 -7
  73. package/dist/validators/past-date.js +8 -11
  74. package/dist/validators/pattern.js +5 -8
  75. package/dist/validators/phone.js +6 -9
  76. package/dist/validators/required.js +4 -7
  77. package/dist/validators/url.js +9 -14
  78. package/dist/validators.js +84 -106
  79. package/docs/llms/02-quick-start.md +7 -7
  80. package/docs/llms/03-api-signatures.md +11 -5
  81. package/docs/llms/05-common-mistakes.md +1 -1
  82. package/docs/llms/09-formschema.md +12 -12
  83. package/docs/llms/10-arrays.md +34 -4
  84. package/docs/llms/14-extended-mistakes.md +1 -1
  85. package/docs/llms/15-project-structure.md +15 -13
  86. package/docs/llms/16-ui-components.md +36 -9
  87. package/docs/llms/17-nonexistent-api.md +16 -2
  88. package/docs/llms/29-async-preload.md +4 -4
  89. package/docs/llms/30-type-safety-recipes.md +1 -1
  90. package/docs/llms/34-i18n.md +272 -0
  91. package/llms-index.json +1 -1
  92. package/llms.txt +1925 -175
  93. package/package.json +22 -3
  94. package/dist/create-model-Dxh9cfLp.js +0 -97
  95. package/dist/model-signals-proxy-mV3WGdlO.js +0 -233
@@ -11,7 +11,7 @@ import { ConfigWithSchema, ConfigWithValue, UnknownRecord } from '../types/index
11
11
  * import { NodeFactory } from '@reformer/core';
12
12
  *
13
13
  * const factory = new NodeFactory();
14
- * const node = factory.createNode({ value: '', component: InputField }); // → FieldNode<string>
14
+ * const node = factory.createNode({ value: '', component: Input }); // → FieldNode<string>
15
15
  * ```
16
16
  */
17
17
  export declare class NodeFactory {
@@ -37,25 +37,25 @@ export declare class NodeFactory {
37
37
  * // FieldNode
38
38
  * const field = factory.createNode({
39
39
  * value: 'test@mail.com',
40
- * component: InputField,
40
+ * component: Input,
41
41
  * validators: [required, email]
42
42
  * });
43
43
  *
44
44
  * // GroupNode
45
45
  * const group = factory.createNode({
46
- * email: { value: '', component: InputField },
47
- * password: { value: '', component: InputField }
46
+ * email: { value: '', component: Input },
47
+ * password: { value: '', component: Input }
48
48
  * });
49
49
  *
50
50
  * // ArrayNode (объект)
51
51
  * const array = factory.createNode({
52
- * schema: { title: { value: '', component: InputField } },
52
+ * schema: { title: { value: '', component: Input } },
53
53
  * initialItems: [{ title: 'Item 1' }]
54
54
  * });
55
55
  *
56
56
  * // ArrayNode (массив) - новый формат
57
57
  * const array2 = factory.createNode([
58
- * { title: { value: '', component: InputField } }, // schema
58
+ * { title: { value: '', component: Input } }, // schema
59
59
  * { title: 'Item 1' }, // initial item 1
60
60
  * { title: 'Item 2' } // initial item 2
61
61
  * ]);
@@ -80,7 +80,7 @@ export declare class NodeFactory {
80
80
  *
81
81
  * // Массив с начальными элементами
82
82
  * const array = factory.createArrayNodeFromArray([
83
- * { title: { value: '', component: InputField } }, // schema
83
+ * { title: { value: '', component: Input } }, // schema
84
84
  * { title: 'Item 1' }, // initial value
85
85
  * { title: 'Item 2' } // initial value
86
86
  * ]);
@@ -94,7 +94,7 @@ export declare class NodeFactory {
94
94
  * ✅ НОВОЕ: Извлечено из GroupNode для централизации логики
95
95
  *
96
96
  * Преобразует схему формы в объект со значениями:
97
- * - `{ name: { value: 'John', component: InputField } } → { name: 'John' }`
97
+ * - `{ name: { value: 'John', component: Input } } → { name: 'John' }`
98
98
  * - Поддерживает вложенные группы
99
99
  * - Поддерживает массивы
100
100
  *
@@ -106,10 +106,10 @@ export declare class NodeFactory {
106
106
  * const factory = new NodeFactory();
107
107
  *
108
108
  * const schema = {
109
- * name: { value: 'John', component: InputField },
110
- * age: { value: 30, component: InputField },
109
+ * name: { value: 'John', component: Input },
110
+ * age: { value: 30, component: Input },
111
111
  * address: {
112
- * city: { value: 'Moscow', component: InputField }
112
+ * city: { value: 'Moscow', component: Input }
113
113
  * }
114
114
  * };
115
115
  *
@@ -132,7 +132,7 @@ export declare class NodeFactory {
132
132
  * ```typescript
133
133
  * const factory = new NodeFactory();
134
134
  *
135
- * factory.isFieldConfig({ value: '', component: InputField }); // true
135
+ * factory.isFieldConfig({ value: '', component: Input }); // true
136
136
  * factory.isFieldConfig({ email: { value: '' } }); // false
137
137
  * factory.isFieldConfig(null); // false
138
138
  * ```
@@ -155,7 +155,7 @@ export declare class NodeFactory {
155
155
  * const factory = new NodeFactory();
156
156
  *
157
157
  * factory.isArrayConfig({ schema: {}, initialItems: [] }); // true
158
- * factory.isArrayConfig({ value: '', component: InputField }); // false
158
+ * factory.isArrayConfig({ value: '', component: Input }); // false
159
159
  * factory.isArrayConfig({ email: { value: '' } }); // false
160
160
  * ```
161
161
  */
@@ -176,11 +176,11 @@ export declare class NodeFactory {
176
176
  * const factory = new NodeFactory();
177
177
  *
178
178
  * factory.isGroupConfig({
179
- * email: { value: '', component: InputField },
180
- * password: { value: '', component: InputField }
179
+ * email: { value: '', component: Input },
180
+ * password: { value: '', component: Input }
181
181
  * }); // true
182
182
  *
183
- * factory.isGroupConfig({ value: '', component: InputField }); // false
183
+ * factory.isGroupConfig({ value: '', component: Input }); // false
184
184
  * factory.isGroupConfig({ schema: {} }); // false
185
185
  * factory.isGroupConfig(null); // false
186
186
  * ```
@@ -11,8 +11,8 @@ import { FormProxy } from '../types/form-proxy';
11
11
  * @example
12
12
  * ```typescript
13
13
  * const array = new ArrayNode({
14
- * title: { value: '', component: InputField },
15
- * price: { value: 0, component: InputField },
14
+ * title: { value: '', component: Input },
15
+ * price: { value: 0, component: Input },
16
16
  * });
17
17
  *
18
18
  * array.push({ title: 'Item 1', price: 100 });
@@ -141,7 +141,7 @@ export declare class ArrayNode<T extends object> extends FormNode<T[]> {
141
141
  * @example
142
142
  * ```typescript
143
143
  * const arrayNode = new ArrayNode(
144
- * { name: { value: '', component: InputField } },
144
+ * { name: { value: '', component: Input } },
145
145
  * [{ name: 'Initial 1' }, { name: 'Initial 2' }]
146
146
  * );
147
147
  *
@@ -11,7 +11,7 @@ import { FieldConfig, FieldStatus, ValidationError } from '../types/index';
11
11
  * ```typescript
12
12
  * const field = new FieldNode({
13
13
  * value: '',
14
- * component: InputField,
14
+ * component: Input,
15
15
  * validators: [required, email],
16
16
  * });
17
17
  *
@@ -103,7 +103,7 @@ export declare class FieldNode<T> extends FormNode<T> {
103
103
  *
104
104
  * @example
105
105
  * ```typescript
106
- * const field = new FieldNode({ value: 'initial', component: InputField });
106
+ * const field = new FieldNode({ value: 'initial', component: Input });
107
107
  *
108
108
  * field.setValue('changed');
109
109
  * field.reset('temp value');
@@ -202,7 +202,7 @@ export declare class FieldNode<T> extends FormNode<T> {
202
202
  * const form = createForm({
203
203
  * email: {
204
204
  * value: '',
205
- * component: InputField,
205
+ * component: Input,
206
206
  * updateOn: 'submit', // Изначально валидация только при submit
207
207
  * validators: [required, email],
208
208
  * },
@@ -15,8 +15,8 @@ import { SubmitOptions, SubmitResult } from '../form-submitter';
15
15
  * @example
16
16
  * ```typescript
17
17
  * const form = new GroupNode({
18
- * email: { valueSignal: model.$.email, component: InputField },
19
- * password: { valueSignal: model.$.password, component: InputField },
18
+ * email: { valueSignal: model.$.email, component: Input },
19
+ * password: { valueSignal: model.$.password, component: Input },
20
20
  * });
21
21
  *
22
22
  * // Прямой доступ к полям через Proxy
@@ -136,8 +136,8 @@ export declare class GroupNode<T> extends FormNode<T> {
136
136
  * @example
137
137
  * ```typescript
138
138
  * const form = new GroupNode({
139
- * email: { value: '', component: InputField },
140
- * name: { value: '', component: InputField },
139
+ * email: { value: '', component: Input },
140
+ * name: { value: '', component: Input },
141
141
  * });
142
142
  *
143
143
  * const proxy = form.getProxy();
@@ -189,11 +189,11 @@ export declare class GroupNode<T> extends FormNode<T> {
189
189
  * @example
190
190
  * ```typescript
191
191
  * const form = new GroupNode({
192
- * email: { value: '', component: InputField },
192
+ * email: { value: '', component: Input },
193
193
  * address: {
194
- * city: { value: '', component: InputField }
194
+ * city: { value: '', component: Input }
195
195
  * },
196
- * items: [{ name: { value: '', component: InputField } }]
196
+ * items: [{ name: { value: '', component: Input } }]
197
197
  * });
198
198
  *
199
199
  * form.getFieldByPath('email'); // FieldNode
@@ -36,8 +36,8 @@ export interface ModelArrayControl<TItem extends object> {
36
36
  * const model = createModel<{ rows: { name: string; qty: number }[] }>({ rows: [] });
37
37
  *
38
38
  * const rowItem = (item: FormModel<{ name: string; qty: number }>) => ({
39
- * name: { value: item.$.name, component: InputField },
40
- * qty: { value: item.$.qty, component: InputField },
39
+ * name: { value: item.$.name, component: Input },
40
+ * qty: { value: item.$.qty, component: Input },
41
41
  * });
42
42
  *
43
43
  * const form = createForm({
@@ -75,7 +75,13 @@ export type AsyncValidatorFn<T = FormValue> = (value: T, options?: AsyncValidato
75
75
  */
76
76
  export interface ValidationError {
77
77
  code: string;
78
+ /**
79
+ * Готовый текст ошибки. Пустая строка — «автор правила текста не задавал»: отображаемую строку
80
+ * тогда даёт резолвер по `code` (словарь локали `validation.<code>`).
81
+ */
78
82
  message: string;
83
+ /** Ключ сообщения в словаре приложения; при наличии в активной локали важнее `message`. */
84
+ messageKey?: string;
79
85
  params?: Record<string, FormValue>;
80
86
  /** Severity level: 'error' (default) blocks submission, 'warning' shows message but allows submission */
81
87
  severity?: 'error' | 'warning';
@@ -89,14 +89,14 @@ type NonEmptyFieldConfig<T> = RequireAtLeastOne<FieldConfig<T>>;
89
89
  * }
90
90
  *
91
91
  * const schema: FormSchema<Form> = {
92
- * name: { value: '', component: InputField },
92
+ * name: { value: '', component: Input },
93
93
  * address: {
94
- * city: { value: '', component: InputField },
95
- * street: { value: '', component: InputField },
94
+ * city: { value: '', component: Input },
95
+ * street: { value: '', component: Input },
96
96
  * },
97
97
  * items: [{
98
- * title: { value: '', component: InputField },
99
- * price: { value: 0, component: InputField },
98
+ * title: { value: '', component: Input },
99
+ * price: { value: 0, component: Input },
100
100
  * }],
101
101
  * };
102
102
  * ```
@@ -17,8 +17,20 @@ export type Validator<TForm, TField> = (value: TField, scope: unknown, root: For
17
17
  */
18
18
  export interface ValidateOptions {
19
19
  /** Готовое сообщение об ошибке. Если не задано, валидаторы кладут `''`, и отображаемый текст
20
- * резолвится из `code` (см. резолвер сообщений в `@reformer/cdk`). */
20
+ * берётся из словаря локали по `code` (`validation.<code>`; см. `resolveValidationError` в
21
+ * `@reformer/core/i18n`). Явное сообщение важнее словаря, но не переключается вместе с языком —
22
+ * для локализуемого текста используйте {@link ValidateOptions.messageKey}. */
21
23
  message?: string;
24
+ /**
25
+ * Ключ сообщения в словаре приложения — локализуемый текст автора правила. Если ключ есть в
26
+ * активной локали, он важнее и `message`, и текста по `code`; если нет — в ход идёт `message`.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * required({ messageKey: 'profile.name.required', message: 'Name is required' });
31
+ * ```
32
+ */
33
+ messageKey?: string;
22
34
  /** Параметры ошибки (подстановка в шаблон сообщения / i18n). */
23
35
  params?: Record<string, FormValue>;
24
36
  }
@@ -3,9 +3,13 @@ import { AsyncRule, Rule, ValidationSchema } from './types';
3
3
  /**
4
4
  * Синхронные правила поля.
5
5
  *
6
+ * Поле — лист либо массив целиком: `model.$.<массив>` — такая же ручка значения, и правило
7
+ * получает массив (мультивыбор, теги, список файлов).
8
+ *
6
9
  * @example
7
10
  * ```ts
8
11
  * validate(model.$.loanAmount, [required({ message: 'Сумма' }), min(50000)]);
12
+ * validate(model.$.tags, [required(), maxLength(3)]); // tags: string[] — массив как значение
9
13
  * ```
10
14
  */
11
15
  export declare function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void;
@@ -35,7 +39,8 @@ export declare function validateWhen(cond: () => boolean, cb: () => void): void;
35
39
  export declare function cross<TSnapshot>(sig: PathAwareSignal<unknown>, fn: (form: TSnapshot) => ValidationError | null): void;
36
40
  /**
37
41
  * Применить под-правила к КАЖДОМУ элементу текущего массива модели.
38
- * `U extends object` — элементы должны быть под-моделями (объектами); для массива примитивов валидируйте лист напрямую.
42
+ * `U extends object` — элементы должны быть под-моделями (объектами); массив примитивов — одно
43
+ * значение поля, его проверяют целиком: `validate(model.$.<массив>, [...])`.
39
44
  */
40
45
  export declare function each<U extends object>(arr: ModelArray<U>, itemFn: (item: FormModel<U>) => void): void;
41
46
  /** Композиция под-схем в текущую (над той же моделью scope). Заменяет пошаговую группировку. */
@@ -0,0 +1,8 @@
1
+ import { FormValue, ValidationError } from '../types/contracts';
2
+ import { ValidateOptions } from '../types/validation-schema';
3
+ /**
4
+ * @param code - Код ошибки; по нему ищется текст в словаре локали (`validation.<code>`).
5
+ * @param options - Опции правила: явное сообщение, ключ сообщения, дополнительные параметры.
6
+ * @param params - Параметры, которые правило заполняет само; параметры из `options` важнее.
7
+ */
8
+ export declare function validationError(code: string, options: ValidateOptions | undefined, params?: Record<string, FormValue>): ValidationError;
@@ -0,0 +1,54 @@
1
+ import { Messages } from './locale';
2
+ /**
3
+ * Бренд описателя. `Symbol.for` — чтобы описатель узнавался и через границу двух копий модуля
4
+ * (разные чанки, разные версии пакета на странице).
5
+ */
6
+ export declare const MESSAGE_DESCRIPTOR: unique symbol;
7
+ /**
8
+ * Описатель сообщения. Намеренно без ключей `value`, `array`, `item`, `component`: обход схемы в
9
+ * `createForm` узнаёт по ним узел, а описатель узлом не является.
10
+ */
11
+ export interface MessageDescriptor {
12
+ readonly [MESSAGE_DESCRIPTOR]: true;
13
+ /** Ключ сообщения в словаре приложения. */
14
+ readonly key: string;
15
+ /** Значения подстановок. */
16
+ readonly values?: Readonly<Record<string, unknown>>;
17
+ /** Английский текст на случай, если ключа в локали нет. */
18
+ readonly defaultMessage?: string;
19
+ }
20
+ /** Текст, который можно задать строкой или описателем. */
21
+ export type LocalizableText = string | MessageDescriptor;
22
+ /**
23
+ * Создаёт описатель сообщения.
24
+ *
25
+ * @param key - Ключ в словаре приложения.
26
+ * @param values - Значения подстановок.
27
+ * @param defaultMessage - Английский текст на случай, если ключа в локали нет.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * { value: model.$.email, component: Input, componentProps: { label: msg('profile.email.label') } }
32
+ * ```
33
+ */
34
+ export declare function msg(key: string, values?: Readonly<Record<string, unknown>>, defaultMessage?: string): MessageDescriptor;
35
+ /** Является ли значение описателем сообщения. */
36
+ export declare function isMessageDescriptor(value: unknown): value is MessageDescriptor;
37
+ /**
38
+ * `msg` с типизированными ключами: принимает английский словарь приложения, проверяет ключ при
39
+ * компиляции и подставляет английский текст как `defaultMessage` — без провайдера и при промахе
40
+ * по локали форма покажет его, а не ключ.
41
+ *
42
+ * @param defaults - Английский словарь приложения.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * import appEn from './locales/en.json';
47
+ *
48
+ * export const { msg } = defineMessages(appEn);
49
+ * msg('profile.email.label'); // опечатка в ключе — ошибка компиляции
50
+ * ```
51
+ */
52
+ export declare function defineMessages<D extends Messages>(defaults: D): {
53
+ msg(key: keyof D & string, values?: Readonly<Record<string, unknown>>): MessageDescriptor;
54
+ };
@@ -0,0 +1,45 @@
1
+ declare const _default: {
2
+ "format.fileSize.b": "{value} B",
3
+ "format.fileSize.kb": "{value} KB",
4
+ "format.fileSize.mb": "{value} MB",
5
+ "format.fileSize.gb": "{value} GB",
6
+ "format.fileSize.tb": "{value} TB",
7
+
8
+ "validation.required": "This field is required",
9
+ "validation.pattern": "Invalid format",
10
+ "validation.email": "Enter a valid email address",
11
+ "validation.url": "Enter a valid URL",
12
+ "validation.url_protocol": "The URL must use one of these protocols: {allowedProtocols}",
13
+ "validation.phone": "Enter a valid phone number",
14
+
15
+ "validation.min": "Must be at least {min}",
16
+ "validation.max": "Must be at most {max}",
17
+ "validation.minLength": "Enter at least {minLength, plural, one{# character} other{# characters}}",
18
+ "validation.maxLength": "Enter no more than {maxLength, plural, one{# character} other{# characters}}",
19
+ "validation.isNumber": "Enter a number",
20
+ "validation.integer": "Enter a whole number",
21
+ "validation.multipleOf": "Must be a multiple of {multipleOf}",
22
+ "validation.nonNegative": "Must not be negative",
23
+ "validation.nonZero": "Must not be zero",
24
+
25
+ "validation.date_invalid": "Enter a valid date",
26
+ "validation.date_min": "The date must not be earlier than {minDate}",
27
+ "validation.date_max": "The date must not be later than {maxDate}",
28
+ "validation.date_past": "The date must not be in the past",
29
+ "validation.date_future": "The date must not be in the future",
30
+ "validation.date_min_age": "Age must be at least {minAge}",
31
+ "validation.date_max_age": "Age must be at most {maxAge}",
32
+
33
+ "validation.fileType": "File {fileName} has a type that is not allowed. Allowed: {accept}",
34
+ "validation.maxFileSize": "File {fileName} is larger than {maxFileSize}",
35
+ "validation.minFileSize": "File {fileName} is smaller than {minFileSize}",
36
+ "validation.maxFiles": "No more than {maxFiles, plural, one{# file} other{# files}}",
37
+ "validation.minFiles": "At least {minFiles, plural, one{# file} other{# files}}",
38
+ "validation.maxTotalFileSize": "The total size of the files exceeds {maxTotalFileSize}",
39
+ "validation.fileExists": "File {fileName} has already been added",
40
+ "validation.uploadAborted": "Upload cancelled",
41
+ "validation.uploadFailed": "Failed to upload {fileName}"
42
+ }
43
+ ;
44
+
45
+ export default _default;
@@ -0,0 +1,71 @@
1
+ import { FormLocale, Messages } from './locale';
2
+ /** Что может вернуть источник: локаль целиком, её часть, голый словарь или ничего. */
3
+ export type LocaleSourceResult = Partial<FormLocale> | Messages | null | undefined;
4
+ /**
5
+ * Источник локали. Получает код языка, отдаёт локаль или словарь — сразу или промисом.
6
+ * `null`/`undefined` означает «для этого языка у меня ничего нет» и ошибкой не считается.
7
+ */
8
+ export type LocaleSource = (code: string) => LocaleSourceResult | Promise<LocaleSourceResult>;
9
+ /** Загрузчик локалей с кэшем. */
10
+ export interface LocaleLoader {
11
+ /** Загрузить локаль языка. Повторный вызов возвращает тот же промис и ту же локаль. */
12
+ (code: string): Promise<FormLocale>;
13
+ /** Уже загруженная локаль или `undefined`. Позволяет провайдеру отрисоваться синхронно. */
14
+ peek(code: string): FormLocale | undefined;
15
+ /** То же, что вызов загрузчика; имя для чтения в коде старта приложения. */
16
+ preload(code: string): Promise<FormLocale>;
17
+ }
18
+ /**
19
+ * Собирает загрузчик из источников. Источники опрашиваются параллельно, а сливаются по порядку:
20
+ * поздний перекрывает ранний. Результат кэшируется по коду языка, идущий запрос не дублируется.
21
+ * Отказ любого источника — отказ загрузки; из кэша такой запрос удаляется, и следующая попытка
22
+ * пойдёт заново. Источник, которому можно промахнуться, приложение оборачивает само:
23
+ * `(code) => source(code).catch(() => null)`.
24
+ *
25
+ * Код языка у результата — всегда запрошенный: по нему работает `Intl`, и он не должен зависеть
26
+ * от того, что написано внутри файла.
27
+ *
28
+ * @param sources - Источники в порядке возрастания приоритета.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { createLocaleLoader, fetchMessages } from '@reformer/core/i18n';
33
+ * import { loadKitLocale } from '@reformer/ui-kit/locale';
34
+ *
35
+ * export const loadLocale = createLocaleLoader([
36
+ * loadKitLocale, // подписи кита — чанк нужного языка
37
+ * fetchMessages((code) => `/locales/${code}.json`), // ключи приложения и правки — с сервера
38
+ * ]);
39
+ * ```
40
+ */
41
+ export declare function createLocaleLoader(sources: readonly LocaleSource[]): LocaleLoader;
42
+ /**
43
+ * Источник локали по сети: забирает JSON по адресу, построенному из кода языка. Файл может быть
44
+ * словарём (`{ "profile.title": "Профиль" }`) или локалью целиком (`{ "code": "ru", "messages": … }`).
45
+ * Ответ не `2xx` — ошибка загрузки.
46
+ *
47
+ * @param url - Адрес файла для языка.
48
+ * @param init - Параметры запроса, передаются в `fetch` как есть.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * fetchMessages((code) => `/locales/${code}.json`);
53
+ * ```
54
+ */
55
+ export declare function fetchMessages(url: (code: string) => string, init?: RequestInit): LocaleSource;
56
+ /**
57
+ * Источник встроенных локалей пакета: по чанку на язык. Язык ищется точно, затем по основной части
58
+ * кода (`ru-RU` → `ru`). Языка нет — источник отдаёт `null`: подписи пакета останутся английскими,
59
+ * пока приложение не даст их своим источником.
60
+ *
61
+ * @param loaders - Код языка → ленивый импорт его локали.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * export const loadKitLocale = bundledLocaleSource({
66
+ * en: () => import('./en').then((m) => m.en),
67
+ * ru: () => import('./ru').then((m) => m.ru),
68
+ * });
69
+ * ```
70
+ */
71
+ export declare function bundledLocaleSource(loaders: Readonly<Record<string, () => Promise<FormLocale>>>): LocaleSource;
@@ -0,0 +1,39 @@
1
+ import { FormLocale } from './locale';
2
+ /** Функция загрузки локали. `peek` — необязательный синхронный кэш (есть у `createLocaleLoader`). */
3
+ export interface LocaleLoad {
4
+ (code: string): Promise<FormLocale>;
5
+ peek?(code: string): FormLocale | undefined;
6
+ }
7
+ /** Состояние хранилища. Объект неизменяемый: новая ссылка — новое состояние. */
8
+ export interface LocaleStoreState {
9
+ /** Запрошенный язык. Пока `pending`, может отличаться от языка `locale`. */
10
+ readonly lang: string;
11
+ /** Локаль, которая сейчас на экране; `null` — ни одна ещё не загружена. */
12
+ readonly locale: FormLocale | null;
13
+ /** Идёт загрузка запрошенного языка. */
14
+ readonly pending: boolean;
15
+ /** Ошибка последней загрузки; сбрасывается следующей удачной. */
16
+ readonly error: unknown;
17
+ }
18
+ /** Хранилище активной локали. */
19
+ export interface LocaleStore {
20
+ getState(): LocaleStoreState;
21
+ subscribe(listener: () => void): () => void;
22
+ /** Запросить язык. Повторный запрос того же языка, пока он грузится или уже показан, — no-op. */
23
+ setLang(lang: string): void;
24
+ }
25
+ /** Настройки хранилища. */
26
+ export interface LocaleStoreOptions {
27
+ /** Вызывается при отказе загрузки языка, который всё ещё запрошен. */
28
+ readonly onError?: (error: unknown, lang: string) => void;
29
+ }
30
+ /**
31
+ * Создаёт хранилище. Загрузка при создании НЕ начинается: начальное состояние считается чисто —
32
+ * из кэша загрузчика, если локаль там уже есть, — а запрос уходит на первый {@link LocaleStore.setLang}.
33
+ * Это позволяет создавать хранилище прямо в рендере.
34
+ *
35
+ * @param load - Загрузчик локалей.
36
+ * @param lang - Начальный язык.
37
+ * @param options - См. {@link LocaleStoreOptions}.
38
+ */
39
+ export declare function createLocaleStore(load: LocaleLoad, lang: string, options?: LocaleStoreOptions): LocaleStore;
@@ -0,0 +1,90 @@
1
+ import { MessagePattern } from './message-format';
2
+ /** Плоский словарь: ключ → сообщение в формате ICU (см. `message-format.ts`). */
3
+ export type Messages = Readonly<Record<string, string>>;
4
+ /** День недели, с которого начинается календарь: `0` — воскресенье, `1` — понедельник. */
5
+ export type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;
6
+ /**
7
+ * Локаль формы.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const ru: FormLocale = {
12
+ * code: 'ru',
13
+ * weekStartsOn: 1,
14
+ * messages: { 'kit.select.selected': 'Выбрано: {count}' },
15
+ * };
16
+ * ```
17
+ */
18
+ export interface FormLocale {
19
+ /** Код языка BCP 47 (`en`, `ru`, `pt-BR`): по нему работают `Intl` и множественные формы. */
20
+ readonly code: string;
21
+ /** Словарь сообщений. Ключа нет — пакет подставит встроенный английский. */
22
+ readonly messages: Messages;
23
+ /** Первый день недели календаря. По умолчанию — воскресенье. */
24
+ readonly weekStartsOn?: WeekStart;
25
+ /**
26
+ * Необязательное переопределение: объект локали `react-day-picker` / `date-fns`. Для ядра
27
+ * непрозрачен. Без него даты форматируются через `Intl` по {@link FormLocale.code}.
28
+ */
29
+ readonly dateLocale?: unknown;
30
+ /** Необязательное переопределение: формат даты в токенах `date-fns` для DatePicker. */
31
+ readonly dateFormat?: string;
32
+ }
33
+ /**
34
+ * Локаль «провайдера нет»: английский, пустой словарь. Каждый пакет при промахе по словарю берёт
35
+ * свою встроенную английскую таблицу, поэтому пустой словарь означает «всё по-английски».
36
+ */
37
+ export declare const DEFAULT_LOCALE: FormLocale;
38
+ /**
39
+ * Новая локаль на основе существующей: словари сливаются (ключи из `patch` важнее), остальные
40
+ * поля заменяются. Исходный объект не меняется.
41
+ *
42
+ * @param base - Локаль-основа, например поставляемая китом.
43
+ * @param patch - Свои ключи, переопределения китовых подписей и поля локали.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { ru } from '@reformer/ui-kit/locale/ru';
48
+ *
49
+ * const appRu = extendLocale(ru, {
50
+ * messages: { 'profile.title': 'Профиль', 'kit.formWizard.submit': 'Отправить' },
51
+ * });
52
+ * ```
53
+ */
54
+ export declare function extendLocale(base: FormLocale, patch: Partial<FormLocale>): FormLocale;
55
+ /** `Object.hasOwn` без требования ES2022 к рантайму потребителя. */
56
+ export declare function hasOwn(target: object, key: string): boolean;
57
+ /** Что не так со словарём. */
58
+ export interface LocaleIssue {
59
+ readonly key: string;
60
+ /**
61
+ * - `syntax` — сообщение не разбирается;
62
+ * - `missing` — ключ есть в образце, но его нет в словаре;
63
+ * - `extra` — ключа нет в образце;
64
+ * - `arguments` — набор подстановок не совпадает с образцом.
65
+ */
66
+ readonly kind: 'syntax' | 'missing' | 'extra' | 'arguments';
67
+ readonly message: string;
68
+ }
69
+ /**
70
+ * Имена аргументов сообщения, включая те, что встречаются только внутри веток `plural`/`select`.
71
+ *
72
+ * @param pattern - Результат `parseMessage`.
73
+ */
74
+ export declare function messageArguments(pattern: MessagePattern, out?: Set<string>): Set<string>;
75
+ /**
76
+ * Проверяет словарь: каждое сообщение разбирается, а при заданном образце — набор ключей и имена
77
+ * подстановок совпадают с ним. Нужна там, где компилятор не поможет: словарь пришёл по сети или
78
+ * написан вне репозитория. В рантайме вызывать не обязательно — битое или пропущенное сообщение
79
+ * и так откатится на встроенный английский; проверка нужна тестам и CI.
80
+ *
81
+ * @param messages - Проверяемый словарь.
82
+ * @param reference - Образец, обычно английский словарь того же пакета.
83
+ * @returns Список расхождений; пустой — словарь в порядке.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * expect(validateLocale(ruJson, enJson)).toEqual([]);
88
+ * ```
89
+ */
90
+ export declare function validateLocale(messages: Messages, reference?: Messages): LocaleIssue[];