@reformer/core 10.2.0 → 11.0.0-beta.10
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 +90 -68
- package/dist/behaviors-value-D05z3my6.js +109 -0
- package/dist/behaviors.d.ts +1 -1
- package/dist/behaviors.js +376 -211
- package/dist/create-model-DPSG3I0O.js +30 -0
- package/dist/date-utils-90-qZbse.js +43 -0
- package/dist/en-HP8XEych.js +44 -0
- package/dist/form/aggregate-signals.d.ts +21 -63
- package/dist/form/behaviors/collections.d.ts +84 -0
- package/dist/form/behaviors/context.d.ts +56 -0
- package/dist/form/behaviors/index.d.ts +30 -0
- package/dist/form/behaviors/internals.d.ts +41 -0
- package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +28 -5
- package/dist/form/behaviors/operators.d.ts +59 -0
- package/dist/form/behaviors/schema-operators.d.ts +83 -0
- package/dist/form/behaviors/types.d.ts +37 -0
- package/dist/form/create-form.d.ts +59 -70
- package/dist/form/flow/flow.d.ts +87 -0
- package/dist/form/form-bundle.d.ts +153 -0
- package/dist/form/form-level.d.ts +121 -0
- package/dist/form/form-proxy-builder.d.ts +6 -22
- package/dist/form/index.d.ts +23 -27
- package/dist/form/nodes/form-array-node.d.ts +100 -0
- package/dist/form/nodes/form-control-node.d.ts +73 -0
- package/dist/form/nodes/form-group-node.d.ts +66 -0
- package/dist/form/nodes/node-kind.d.ts +29 -0
- package/dist/form/nodes/state/availability.d.ts +6 -0
- package/dist/form/nodes/state/component.d.ts +6 -0
- package/dist/form/nodes/state/index.d.ts +12 -0
- package/dist/form/nodes/state/interaction.d.ts +13 -0
- package/dist/form/nodes/state/issues.d.ts +9 -0
- package/dist/form/nodes/state/pending.d.ts +6 -0
- package/dist/form/schema/compile.d.ts +59 -0
- package/dist/form/schema/compiled.d.ts +143 -0
- package/dist/form/schema/row-key.d.ts +11 -0
- package/dist/form/schema-controller.d.ts +159 -0
- package/dist/form/schema-subtree.d.ts +28 -0
- package/dist/form/signal-node-registry.d.ts +27 -22
- package/dist/form/submission.d.ts +93 -0
- package/dist/form/type-guards.d.ts +14 -73
- package/dist/form/types/contracts.d.ts +21 -91
- package/dist/form/types/field-config.d.ts +27 -0
- package/dist/form/types/index.d.ts +5 -91
- package/dist/form/types/nodes.d.ts +159 -0
- package/dist/form/types/schema-node.d.ts +148 -50
- package/dist/form/types/validation-schema.d.ts +24 -41
- package/dist/form/validation/context.d.ts +67 -0
- package/dist/form/validation/index.d.ts +24 -28
- package/dist/form/validation/operators.d.ts +101 -0
- package/dist/form/validation/run.d.ts +120 -0
- package/dist/form/validation/strategy.d.ts +89 -0
- package/dist/form/validation/types.d.ts +83 -0
- package/dist/form/validators/date-utils.d.ts +42 -0
- package/dist/form/validators/email.d.ts +23 -0
- package/dist/form/{validation/validators → validators}/file-type.d.ts +10 -10
- package/dist/form/{validation/validators → validators}/file-utils.d.ts +3 -2
- package/dist/form/validators/future-date.d.ts +23 -0
- package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
- package/dist/form/validators/integer.d.ts +22 -0
- package/dist/form/validators/is-date.d.ts +24 -0
- package/dist/form/validators/is-number.d.ts +25 -0
- package/dist/form/validators/max-age.d.ts +28 -0
- package/dist/form/validators/max-date.d.ts +25 -0
- package/dist/form/{validation/validators → validators}/max-file-size.d.ts +10 -10
- package/dist/form/validators/max-files.d.ts +26 -0
- package/dist/form/validators/max-length.d.ts +33 -0
- package/dist/form/validators/max-total-file-size.d.ts +26 -0
- package/dist/form/validators/max.d.ts +26 -0
- package/dist/form/validators/min-age.d.ts +27 -0
- package/dist/form/validators/min-date.d.ts +25 -0
- package/dist/form/{validation/validators → validators}/min-file-size.d.ts +10 -10
- package/dist/form/validators/min-files.d.ts +27 -0
- package/dist/form/validators/min-length.d.ts +33 -0
- package/dist/form/validators/min.d.ts +26 -0
- package/dist/form/validators/multiple-of.d.ts +25 -0
- package/dist/form/validators/non-negative.d.ts +22 -0
- package/dist/form/validators/non-zero.d.ts +22 -0
- package/dist/form/validators/past-date.d.ts +23 -0
- package/dist/form/validators/pattern.d.ts +26 -0
- package/dist/form/{validation/validators → validators}/phone.d.ts +11 -11
- package/dist/form/validators/required.d.ts +29 -0
- package/dist/form/validators/url.d.ts +45 -0
- package/dist/form/validators/validation-issue.d.ts +8 -0
- package/dist/i18n/descriptor.d.ts +54 -0
- package/dist/i18n/en.json.d.ts +49 -0
- package/dist/i18n/loader.d.ts +71 -0
- package/dist/i18n/locale-store.d.ts +46 -0
- package/dist/i18n/locale.d.ts +90 -0
- package/dist/i18n/message-format.d.ts +136 -0
- package/dist/i18n/resolve-localized.d.ts +17 -0
- package/dist/i18n/ru.json.d.ts +49 -0
- package/dist/i18n/translator.d.ts +61 -0
- package/dist/i18n/validation-message.d.ts +53 -0
- package/dist/i18n.d.ts +26 -0
- package/dist/i18n.js +86 -0
- package/dist/index.d.ts +18 -6
- package/dist/index.js +932 -2779
- package/dist/loader-BWkigpVe.js +52 -0
- package/dist/locale/en.d.ts +2 -0
- package/dist/locale/en.js +5 -0
- package/dist/locale/en.json +45 -0
- package/dist/locale/index.d.ts +18 -0
- package/dist/locale/ru.d.ts +2 -0
- package/dist/locale/ru.js +44 -0
- package/dist/locale/ru.json +45 -0
- package/dist/locale-CG54AU2Q.js +219 -0
- package/dist/locale.d.ts +2 -0
- package/dist/locale.js +9 -0
- package/dist/{state → model}/behaviors-value.d.ts +24 -6
- package/dist/{state/form-model.d.ts → model/create-model.d.ts} +18 -4
- package/dist/{state → model}/derived-registry.d.ts +1 -1
- package/dist/model/index.d.ts +42 -0
- package/dist/model/model-nodes.d.ts +190 -0
- package/dist/model/model-signals-proxy.d.ts +63 -0
- package/dist/model/model-value-proxy.d.ts +84 -0
- package/dist/{state → model}/safe-effect.d.ts +50 -2
- package/dist/model/types.d.ts +239 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +27 -0
- package/dist/node-kind-C-r6ZMdo.js +15 -0
- package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
- package/dist/platforms/react/field-binding/field-handle.d.ts +53 -0
- package/dist/platforms/react/form-bundle-context.d.ts +27 -0
- package/dist/{form → platforms/react}/hooks/types.d.ts +60 -38
- package/dist/platforms/react/hooks/use-form-bundle.d.ts +41 -0
- package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
- package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +13 -7
- package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +6 -6
- package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +7 -9
- package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +18 -18
- package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +9 -1
- package/dist/platforms/react/i18n/context.d.ts +18 -0
- package/dist/platforms/react/i18n/hooks.d.ts +65 -0
- package/dist/platforms/react/i18n/provider.d.ts +59 -0
- package/dist/platforms/react/index.d.ts +30 -0
- package/dist/react.d.ts +22 -0
- package/dist/react.js +505 -0
- package/dist/runtime-token.d.ts +22 -0
- package/dist/safe-effect-BazLOv1g.js +669 -0
- package/dist/schema-controller-CpiO2JoK.js +143 -0
- package/dist/signals.d.ts +15 -9
- package/dist/signals.js +11 -6
- package/dist/strategy-DzvnsWLh.js +375 -0
- package/dist/type-guards-D-N_5Ddh.js +19 -0
- package/dist/validation-issue-B9mjI8jz.js +7 -0
- package/dist/validation-message-Ced9MGxs.js +194 -0
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +11 -165
- package/dist/validators/email.d.ts +1 -1
- package/dist/validators/email.js +5 -8
- package/dist/validators/future-date.d.ts +1 -1
- package/dist/validators/future-date.js +10 -13
- package/dist/validators/integer.d.ts +1 -1
- package/dist/validators/integer.js +4 -7
- package/dist/validators/is-date.d.ts +1 -1
- package/dist/validators/is-date.js +5 -8
- package/dist/validators/is-number.d.ts +1 -1
- package/dist/validators/is-number.js +4 -7
- package/dist/validators/max-age.d.ts +1 -1
- package/dist/validators/max-age.js +8 -12
- package/dist/validators/max-date.d.ts +1 -1
- package/dist/validators/max-date.js +10 -13
- package/dist/validators/max-length.d.ts +1 -1
- package/dist/validators/max-length.js +7 -10
- package/dist/validators/max.d.ts +1 -1
- package/dist/validators/max.js +4 -7
- package/dist/validators/min-age.d.ts +1 -1
- package/dist/validators/min-age.js +10 -11
- package/dist/validators/min-date.d.ts +1 -1
- package/dist/validators/min-date.js +8 -11
- package/dist/validators/min-length.d.ts +1 -1
- package/dist/validators/min-length.js +4 -7
- package/dist/validators/min.d.ts +1 -1
- package/dist/validators/min.js +4 -7
- package/dist/validators/multiple-of.d.ts +1 -1
- package/dist/validators/multiple-of.js +11 -10
- package/dist/validators/non-negative.d.ts +1 -1
- package/dist/validators/non-negative.js +2 -5
- package/dist/validators/non-zero.d.ts +1 -1
- package/dist/validators/non-zero.js +4 -7
- package/dist/validators/past-date.d.ts +1 -1
- package/dist/validators/past-date.js +10 -13
- package/dist/validators/pattern.d.ts +1 -1
- package/dist/validators/pattern.js +5 -8
- package/dist/validators/phone.d.ts +1 -1
- package/dist/validators/phone.js +10 -11
- package/dist/validators/required.d.ts +1 -1
- package/dist/validators/required.js +4 -7
- package/dist/validators/url.d.ts +1 -1
- package/dist/validators/url.js +48 -17
- package/dist/validators.d.ts +1 -5
- package/dist/validators.js +98 -154
- package/docs/llms/01-api-reference.md +89 -0
- package/docs/llms/02-quick-start.md +181 -0
- package/docs/llms/03-api-signatures.md +494 -0
- package/docs/llms/04-common-patterns.md +204 -0
- package/docs/llms/05-common-mistakes.md +192 -0
- package/docs/llms/06-troubleshooting.md +16 -0
- package/docs/llms/07-complete-import.md +130 -0
- package/docs/llms/08-form-types.md +54 -0
- package/docs/llms/09-formschema.md +191 -0
- package/docs/llms/10-arrays.md +241 -0
- package/docs/llms/11-async-watchfield.md +53 -0
- package/docs/llms/12-array-cleanup.md +31 -0
- package/docs/llms/13-multi-step.md +162 -0
- package/docs/llms/14-extended-mistakes.md +101 -0
- package/docs/llms/15-project-structure.md +279 -0
- package/docs/llms/16-ui-components.md +229 -0
- package/docs/llms/17-nonexistent-api.md +278 -0
- package/docs/llms/18-conditional-fields.md +222 -0
- package/docs/llms/19-reading-values.md +100 -0
- package/docs/llms/20-compute-vs-watch.md +153 -0
- package/docs/llms/21-array-operations.md +163 -0
- package/docs/llms/22-cycle-detection.md +86 -0
- package/docs/llms/23-copy-from.md +141 -0
- package/docs/llms/24-sync-fields.md +115 -0
- package/docs/llms/25-reset-when.md +122 -0
- package/docs/llms/26-transform-value.md +136 -0
- package/docs/llms/27-revalidate-when.md +133 -0
- package/docs/llms/28-submit-and-reset.md +409 -0
- package/docs/llms/29-async-preload.md +203 -0
- package/docs/llms/30-type-safety-recipes.md +213 -0
- package/docs/llms/31-async-validator-debounce.md +178 -0
- package/docs/llms/32-async-options-loading.md +108 -0
- package/docs/llms/33-validation-strategy.md +305 -0
- package/docs/llms/34-i18n.md +298 -0
- package/docs/llms/35-unified-contract.md +309 -0
- package/docs/llms/36-issue-messages.md +284 -0
- package/docs/llms/37-compiled-schema.md +332 -0
- package/docs/llms/38-form-flow.md +396 -0
- package/llms-index.json +1 -0
- package/llms.txt +10014 -3826
- package/package.json +77 -15
- package/dist/behaviors-node-DGEFMTQu.js +0 -18
- package/dist/behaviors-value-C7dSEhCc.js +0 -134
- package/dist/date-utils-dTT_x_Be.js +0 -31
- package/dist/derived-registry-CluZCQ7U.js +0 -16
- package/dist/form/behaviors.d.ts +0 -133
- package/dist/form/error-handler.d.ts +0 -166
- package/dist/form/factories/index.d.ts +0 -6
- package/dist/form/factories/node-factory.d.ts +0 -188
- package/dist/form/form-submitter.d.ts +0 -125
- package/dist/form/nodes/array-node.d.ts +0 -360
- package/dist/form/nodes/field-node.d.ts +0 -294
- package/dist/form/nodes/form-node.d.ts +0 -340
- package/dist/form/nodes/group-node.d.ts +0 -288
- package/dist/form/nodes/model-array-node.d.ts +0 -121
- package/dist/form/status-machine.d.ts +0 -153
- package/dist/form/types/deep-schema.d.ts +0 -116
- package/dist/form/types/form-proxy.d.ts +0 -141
- package/dist/form/unique-id.d.ts +0 -53
- package/dist/form/validation/validators/date-utils.d.ts +0 -30
- package/dist/form/validation/validators/email.d.ts +0 -23
- package/dist/form/validation/validators/future-date.d.ts +0 -23
- package/dist/form/validation/validators/integer.d.ts +0 -22
- package/dist/form/validation/validators/is-date.d.ts +0 -23
- package/dist/form/validation/validators/is-number.d.ts +0 -24
- package/dist/form/validation/validators/max-age.d.ts +0 -25
- package/dist/form/validation/validators/max-date.d.ts +0 -25
- package/dist/form/validation/validators/max-files.d.ts +0 -26
- package/dist/form/validation/validators/max-length.d.ts +0 -27
- package/dist/form/validation/validators/max-total-file-size.d.ts +0 -26
- package/dist/form/validation/validators/max.d.ts +0 -25
- package/dist/form/validation/validators/min-age.d.ts +0 -25
- package/dist/form/validation/validators/min-date.d.ts +0 -25
- package/dist/form/validation/validators/min-files.d.ts +0 -26
- package/dist/form/validation/validators/min-length.d.ts +0 -27
- package/dist/form/validation/validators/min.d.ts +0 -25
- package/dist/form/validation/validators/multiple-of.d.ts +0 -24
- package/dist/form/validation/validators/non-negative.d.ts +0 -22
- package/dist/form/validation/validators/non-zero.d.ts +0 -22
- package/dist/form/validation/validators/past-date.d.ts +0 -23
- package/dist/form/validation/validators/pattern.d.ts +0 -33
- package/dist/form/validation/validators/required.d.ts +0 -32
- package/dist/form/validation/validators/url.d.ts +0 -43
- package/dist/form/validation-schema.d.ts +0 -106
- package/dist/form/validation-strategy.d.ts +0 -55
- package/dist/form/validators.d.ts +0 -6
- package/dist/form-model-DYCzGGKF.js +0 -286
- package/dist/state/index.d.ts +0 -31
- package/dist/state/subscription-manager.d.ts +0 -180
- package/dist/state/types.d.ts +0 -144
- package/dist/state.d.ts +0 -2
- package/dist/state.js +0 -21
- package/dist/subscription-manager-705bls97.js +0 -171
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RuleOptions } from '../types/validation-schema';
|
|
2
|
+
import { Rule } from '../validation/types';
|
|
2
3
|
/**
|
|
3
4
|
* Формат проверки номера телефона для валидатора {@link phone}.
|
|
4
5
|
*
|
|
@@ -9,10 +10,10 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
9
10
|
*/
|
|
10
11
|
export type PhoneFormat = 'international' | 'ru' | 'us' | 'any';
|
|
11
12
|
/**
|
|
12
|
-
* Опции валидатора {@link phone}. Расширяют {@link
|
|
13
|
+
* Опции валидатора {@link phone}. Расширяют {@link RuleOptions} (`code`, `params`)
|
|
13
14
|
* выбором формата номера.
|
|
14
15
|
*/
|
|
15
|
-
export interface PhoneValidatorOptions extends
|
|
16
|
+
export interface PhoneValidatorOptions extends RuleOptions {
|
|
16
17
|
/** Формат проверки {@link PhoneFormat}. По умолчанию `'any'`. */
|
|
17
18
|
format?: PhoneFormat;
|
|
18
19
|
}
|
|
@@ -24,18 +25,17 @@ export interface PhoneValidatorOptions extends ValidateOptions {
|
|
|
24
25
|
*
|
|
25
26
|
* @param options - Опции валидатора {@link PhoneValidatorOptions}. В `params` ошибки
|
|
26
27
|
* автоматически попадает выбранный `format`.
|
|
27
|
-
* @returns
|
|
28
|
+
* @returns Правило {@link Rule} для строкового поля
|
|
28
29
|
*
|
|
29
30
|
* @example Проверка номера телефона
|
|
30
31
|
* ```typescript
|
|
32
|
+
* import { defineValidation, validate } from '@reformer/core/validation';
|
|
31
33
|
* import { required, phone } from '@reformer/core/validators';
|
|
32
34
|
*
|
|
33
|
-
* //
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* validators: [required(), phone({ format: 'ru' })],
|
|
38
|
-
* },
|
|
35
|
+
* // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
36
|
+
* const validation = defineValidation<MyForm>(({ model }) => {
|
|
37
|
+
* validate(model.$.phone, [required(), phone({ format: 'ru' })]);
|
|
38
|
+
* });
|
|
39
39
|
* ```
|
|
40
40
|
*/
|
|
41
|
-
export declare function phone
|
|
41
|
+
export declare function phone(options?: PhoneValidatorOptions): Rule<string | null | undefined>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { RuleOptions } from '../types/validation-schema';
|
|
2
|
+
import { Rule } from '../validation/types';
|
|
3
|
+
/**
|
|
4
|
+
* Фабрика валидатора обязательного поля.
|
|
5
|
+
*
|
|
6
|
+
* Возвращает чистую функцию-валидатор `(value, control, root)`. Передаётся в `validate()`.
|
|
7
|
+
*
|
|
8
|
+
* Пустыми считаются: `null`, `undefined`, `''` (пустая строка), `[]` (пустой массив —
|
|
9
|
+
* обязательный multi-select / FieldArray без выбранных элементов), `NaN` (так выглядит очищенный
|
|
10
|
+
* числовой ввод).
|
|
11
|
+
* Для boolean полей требуется значение `true`.
|
|
12
|
+
*
|
|
13
|
+
* @param options - Опции валидатора ({@link RuleOptions}): `code`, `params`
|
|
14
|
+
* @returns Правило {@link Rule} для поля схемы
|
|
15
|
+
*
|
|
16
|
+
* @example Обязательные поля в схеме формы
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { defineValidation, validate } from '@reformer/core/validation';
|
|
19
|
+
* import { required } from '@reformer/core/validators';
|
|
20
|
+
*
|
|
21
|
+
* // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
22
|
+
* const validation = defineValidation<MyForm>(({ model }) => {
|
|
23
|
+
* validate(model.$.email, [required()]);
|
|
24
|
+
* validate(model.$.phone, [required({ code: 'phone.required' })]);
|
|
25
|
+
* validate(model.$.agreeToTerms, [required({ code: 'agreeToTerms.required' })]);
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function required(options?: RuleOptions): Rule<unknown>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RuleOptions } from '../types/validation-schema';
|
|
2
|
+
import { Rule } from '../validation/types';
|
|
3
|
+
/**
|
|
4
|
+
* Опции валидатора {@link url}. Расширяют {@link RuleOptions} (`code`, `params`)
|
|
5
|
+
* настройками проверки протокола.
|
|
6
|
+
*/
|
|
7
|
+
export interface UrlValidatorOptions extends RuleOptions {
|
|
8
|
+
/** Требовать протокол (`схема://`). По умолчанию `false`: `example.com` допустим. */
|
|
9
|
+
requireProtocol?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Разрешённые протоколы (без `://`), сравнение без учёта регистра, например `['https']`. Не
|
|
12
|
+
* задан или пуст — `http` и `https`. Значение с иным протоколом даёт замечание `url_protocol`;
|
|
13
|
+
* при заданном списке — и значение без протокола.
|
|
14
|
+
*/
|
|
15
|
+
allowedProtocols?: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Фабрика валидатора URL.
|
|
19
|
+
*
|
|
20
|
+
* Пустые значения (`''`/`null`/`undefined`) пропускаются (используйте {@link required}
|
|
21
|
+
* для обязательности).
|
|
22
|
+
*
|
|
23
|
+
* Контракт адреса: разбор — `new URL`; хост — `localhost`, домен с буквенной зоной (в том числе
|
|
24
|
+
* кириллический и punycode), IPv4 или `[IPv6]`; порт допустим. Запрещены пробелы и userinfo
|
|
25
|
+
* (`user:pass@`). Протокол необязателен (`example.com`), пока не задан `requireProtocol`; по
|
|
26
|
+
* умолчанию разрешены `http` и `https`, чужой протокол — замечание `url_protocol` с
|
|
27
|
+
* `params.allowedProtocols`. Свой `code` из опций заменяет оба встроенных кода.
|
|
28
|
+
*
|
|
29
|
+
* @param options - Опции валидатора {@link UrlValidatorOptions}
|
|
30
|
+
* @returns Правило {@link Rule} для строкового поля
|
|
31
|
+
*
|
|
32
|
+
* @example Проверка URL
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { defineValidation, validate } from '@reformer/core/validation';
|
|
35
|
+
* import { required, url } from '@reformer/core/validators';
|
|
36
|
+
*
|
|
37
|
+
* // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
38
|
+
* const validation = defineValidation<MyForm>(({ model }) => {
|
|
39
|
+
* validate(model.$.website, [required(), url({ code: 'website.url' })]);
|
|
40
|
+
* // Требовать протокол и ограничить схему только https:
|
|
41
|
+
* validate(model.$.homepage, [url({ requireProtocol: true, allowedProtocols: ['https'] })]);
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function url(options?: UrlValidatorOptions): Rule<string | null | undefined>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ValidationIssue } from '../types/contracts';
|
|
2
|
+
import { RuleOptions } from '../types/validation-schema';
|
|
3
|
+
/**
|
|
4
|
+
* @param code - Встроенный код правила; по нему ищется текст в словаре локали (`validation.<code>`).
|
|
5
|
+
* @param options - Опции правила: свой код и дополнительные параметры.
|
|
6
|
+
* @param params - Параметры, которые правило заполняет само; параметры из `options` важнее.
|
|
7
|
+
*/
|
|
8
|
+
export declare function validationIssue(code: string, options: RuleOptions | undefined, params?: Record<string, unknown>): ValidationIssue;
|
|
@@ -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
|
+
* { model: 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,49 @@
|
|
|
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.minItems": "At least {minLength, plural, one{# item} other{# items}}",
|
|
20
|
+
"validation.maxItems": "No more than {maxLength, plural, one{# item} other{# items}}",
|
|
21
|
+
"validation.isNumber": "Enter a number",
|
|
22
|
+
"validation.integer": "Enter a whole number",
|
|
23
|
+
"validation.multipleOf": "Must be a multiple of {multipleOf}",
|
|
24
|
+
"validation.nonNegative": "Must not be negative",
|
|
25
|
+
"validation.nonZero": "Must not be zero",
|
|
26
|
+
|
|
27
|
+
"validation.date_invalid": "Enter a valid date",
|
|
28
|
+
"validation.date_min": "The date must not be earlier than {minDate}",
|
|
29
|
+
"validation.date_max": "The date must not be later than {maxDate}",
|
|
30
|
+
"validation.date_past": "The date must not be in the past",
|
|
31
|
+
"validation.date_future": "The date must not be in the future",
|
|
32
|
+
"validation.date_min_age": "Age must be at least {minAge}",
|
|
33
|
+
"validation.date_max_age": "Age must be at most {maxAge}",
|
|
34
|
+
|
|
35
|
+
"validation.fileType": "File {fileName} has a type that is not allowed. Allowed: {accept}",
|
|
36
|
+
"validation.maxFileSize": "File {fileName} is larger than {maxFileSize}",
|
|
37
|
+
"validation.minFileSize": "File {fileName} is smaller than {minFileSize}",
|
|
38
|
+
"validation.maxFiles": "No more than {maxFiles, plural, one{# file} other{# files}}",
|
|
39
|
+
"validation.minFiles": "At least {minFiles, plural, one{# file} other{# files}}",
|
|
40
|
+
"validation.maxTotalFileSize": "The total size of the files exceeds {maxTotalFileSize}",
|
|
41
|
+
"validation.fileExists": "File {fileName} has already been added",
|
|
42
|
+
"validation.uploadAborted": "Upload cancelled",
|
|
43
|
+
"validation.uploadFailed": "Failed to upload {fileName}",
|
|
44
|
+
|
|
45
|
+
"validation.ruleFailed": "Could not check this field. Try again"
|
|
46
|
+
}
|
|
47
|
+
;
|
|
48
|
+
|
|
49
|
+
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,46 @@
|
|
|
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
|
+
/** Повторить запрос языка, загрузка которого упала. Без ошибки — no-op. */
|
|
25
|
+
retry(): void;
|
|
26
|
+
}
|
|
27
|
+
/** Настройки хранилища. */
|
|
28
|
+
export interface LocaleStoreOptions {
|
|
29
|
+
/** Вызывается при отказе загрузки языка, который всё ещё запрошен. */
|
|
30
|
+
readonly onError?: (error: unknown, lang: string) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Локаль, которая уже на экране (прежнее хранилище провайдера с другим загрузчиком). Пока новый
|
|
33
|
+
* загрузчик не отдал язык, на экране остаётся она, а не «ничего не загружено».
|
|
34
|
+
*/
|
|
35
|
+
readonly previous?: FormLocale;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Создаёт хранилище. Загрузка при создании НЕ начинается: начальное состояние считается чисто —
|
|
39
|
+
* из кэша загрузчика, если локаль там уже есть, — а запрос уходит на первый {@link LocaleStore.setLang}.
|
|
40
|
+
* Это позволяет создавать хранилище прямо в рендере.
|
|
41
|
+
*
|
|
42
|
+
* @param load - Загрузчик локалей.
|
|
43
|
+
* @param lang - Начальный язык.
|
|
44
|
+
* @param options - См. {@link LocaleStoreOptions}.
|
|
45
|
+
*/
|
|
46
|
+
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[];
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Формат сообщений интерфейса — подмножество ICU MessageFormat без зависимостей.
|
|
3
|
+
*
|
|
4
|
+
* Один на подписи форм (`@reformer/core/i18n`) и на интерфейс билдера. Поддерживает ровно то, что
|
|
5
|
+
* им нужно, и ничего сверх:
|
|
6
|
+
* - **подстановка** — `Открыт файл {name}`;
|
|
7
|
+
* - **множественные формы** — `{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}`,
|
|
8
|
+
* категории берутся из {@link Intl.PluralRules} активной локали, `#` подставляет само число через
|
|
9
|
+
* {@link Intl.NumberFormat};
|
|
10
|
+
* - **выбор по значению** — `{gender, select, male{он} female{она} other{оно}}`;
|
|
11
|
+
* - **экранирование** — апострофом, как в ICU: `'{'` → `{`, `'}'` → `}`, `''` → `'`; как в
|
|
12
|
+
* `intl-messageformat`, литерал открывает и апостроф перед `<` и `>`.
|
|
13
|
+
*
|
|
14
|
+
* Три свойства, ради которых формат устроен именно так:
|
|
15
|
+
*
|
|
16
|
+
* 1. **Сообщение разбирается в дерево, а не форматируется заменами по строке.** Значение аргумента
|
|
17
|
+
* попадает в результат как текст и повторно не разбирается: `{name}` со значением
|
|
18
|
+
* `{count, plural, ...}` выведет эти символы, а не выполнит их. Замена регулярным выражением
|
|
19
|
+
* такой гарантии не даёт.
|
|
20
|
+
* 2. **Синтаксис — честное подмножество ICU.** Любое сообщение, которое понимает этот модуль,
|
|
21
|
+
* `intl-messageformat` (в режиме `ignoreTag`, со строковыми значениями и конечными числами;
|
|
22
|
+
* значение `plural` — число) понимает так же и выводит посимвольно то же самое; это проверено
|
|
23
|
+
* дифференциальным и property-тестами (`tests/i18n/message-format.differential.test.ts`,
|
|
24
|
+
* `message-format.property.test.ts`). Чего не понимает эталон — повтор ключа ветки, `=N` с не
|
|
25
|
+
* целым числом или в `select`, символы синтаксиса ICU в имени, — не понимает и этот модуль.
|
|
26
|
+
* Отсюда страховка из контракта:
|
|
27
|
+
* если подмножества перестанет хватать, библиотека подставляется под тот же вызов, и словари
|
|
28
|
+
* переписывать не придётся.
|
|
29
|
+
* 3. **Ошибка синтаксиса и отсутствие ветки `other` — отказ на разборе**, а не молчаливая
|
|
30
|
+
* неправильная строка в интерфейсе. Разбор словаря на старте и в CI ловит их до пользователя.
|
|
31
|
+
*
|
|
32
|
+
* Отличий от `intl-messageformat` в выводе нет; отличий два, и оба — сужение, а не расхождение:
|
|
33
|
+
* - **Грамматика уже.** Типы аргументов `number`, `date`, `time`, `selectordinal` не поддержаны и
|
|
34
|
+
* отвергаются на разборе. Сообщение с ними не пройдёт в словарь, а не отформатируется молча
|
|
35
|
+
* неправильно. Заметь: голое `{n}` форматируется как `String(n)` и здесь, и в `intl-messageformat`
|
|
36
|
+
* (`{year}` → `2026`, а не `2 026`); по локали форматируется только `#` внутри `plural`.
|
|
37
|
+
* Вложенность аргументов — не глубже 100 уровней: глубже рекурсивный разбор исчерпал бы стек
|
|
38
|
+
* (`RangeError`) вместо внятного отказа.
|
|
39
|
+
* - **Пропущенный аргумент не бросает исключение**, а подставляет заметный маркер `⟦name⟧`: строка
|
|
40
|
+
* интерфейса не должна ронять панель. Поведение настраивается через `onMissingArgument`.
|
|
41
|
+
*
|
|
42
|
+
* @module i18n/message-format
|
|
43
|
+
*/
|
|
44
|
+
/** Узел разобранного сообщения. */
|
|
45
|
+
export type MessageNode = {
|
|
46
|
+
readonly kind: 'text';
|
|
47
|
+
readonly value: string;
|
|
48
|
+
} | {
|
|
49
|
+
readonly kind: 'arg';
|
|
50
|
+
readonly name: string;
|
|
51
|
+
} | {
|
|
52
|
+
readonly kind: 'number';
|
|
53
|
+
readonly name: string;
|
|
54
|
+
} | {
|
|
55
|
+
readonly kind: 'plural';
|
|
56
|
+
readonly name: string;
|
|
57
|
+
readonly branches: MessageBranches;
|
|
58
|
+
} | {
|
|
59
|
+
readonly kind: 'select';
|
|
60
|
+
readonly name: string;
|
|
61
|
+
readonly branches: MessageBranches;
|
|
62
|
+
};
|
|
63
|
+
/** Разобранное сообщение — плоская последовательность узлов. */
|
|
64
|
+
export type MessagePattern = readonly MessageNode[];
|
|
65
|
+
/** Ветки `plural`/`select`: категория CLDR (`one`, `few`, …), точное совпадение (`=0`) или значение `select`. */
|
|
66
|
+
export type MessageBranches = ReadonlyMap<string, MessagePattern>;
|
|
67
|
+
/** Значения аргументов сообщения. */
|
|
68
|
+
export type MessageValues = Readonly<Record<string, unknown>>;
|
|
69
|
+
/** Настройки форматирования. */
|
|
70
|
+
export interface MessageFormatOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Что подставить вместо аргумента, которого нет в значениях (или чьё значение не число там, где
|
|
73
|
+
* ожидалось число). По умолчанию — заметный маркер `⟦name⟧`, тот же приём, что и для пропущенного
|
|
74
|
+
* ключа: пропуск должен попадаться разработчику, а не пользователю.
|
|
75
|
+
*/
|
|
76
|
+
readonly onMissingArgument?: (name: string) => string;
|
|
77
|
+
}
|
|
78
|
+
/** Отказ разбора сообщения: синтаксис или отсутствие обязательной ветки `other`. */
|
|
79
|
+
export declare class MessageSyntaxError extends Error {
|
|
80
|
+
readonly name = "MessageSyntaxError";
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Код языка, пригодный для `Intl`. Некорректный (не BCP 47) заменяется на `en` — с предупреждением
|
|
84
|
+
* в dev, один раз на код. Код может прийти извне (язык из адресной строки), и `RangeError` из
|
|
85
|
+
* `Intl` ронял бы рендер на первом же `plural`, числе или дате.
|
|
86
|
+
*
|
|
87
|
+
* @param code - Код языка локали.
|
|
88
|
+
* @returns Тот же код либо `en`.
|
|
89
|
+
*/
|
|
90
|
+
export declare function intlLocale(code: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* Значение как текст. Значение, которое нельзя привести к строке (`Object.create(null)`, объект
|
|
93
|
+
* с бросающим `toString`), — пустая строка: одно такое значение не роняет всё сообщение.
|
|
94
|
+
*/
|
|
95
|
+
export declare function toText(value: unknown): string;
|
|
96
|
+
/**
|
|
97
|
+
* Разбирает сообщение в дерево. Бросает {@link MessageSyntaxError} на ошибке синтаксиса и на
|
|
98
|
+
* `plural`/`select` без ветки `other`.
|
|
99
|
+
*
|
|
100
|
+
* @param source - Текст сообщения из словаря.
|
|
101
|
+
* @returns Разобранное сообщение для {@link formatPattern}.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* parseMessage('{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}');
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export declare function parseMessage(source: string): MessagePattern;
|
|
109
|
+
/**
|
|
110
|
+
* Собирает строку из разобранного сообщения. Значения подставляются как текст и повторно
|
|
111
|
+
* не разбираются.
|
|
112
|
+
*
|
|
113
|
+
* @param pattern - Результат {@link parseMessage}.
|
|
114
|
+
* @param values - Значения аргументов.
|
|
115
|
+
* @param locale - Локаль для {@link Intl.PluralRules} и {@link Intl.NumberFormat} (`ru`, `en`, …).
|
|
116
|
+
* @param options - Настройки; см. {@link MessageFormatOptions}.
|
|
117
|
+
* @returns Готовая строка интерфейса.
|
|
118
|
+
*/
|
|
119
|
+
export declare function formatPattern(pattern: MessagePattern, values: MessageValues, locale: string, options?: MessageFormatOptions): string;
|
|
120
|
+
/**
|
|
121
|
+
* Форматтер одной локали с кэшем разбора: сообщение разбирается один раз, на первый вызов.
|
|
122
|
+
* Это внутренность `I18nService.t()` — сам `t()` добавляет сверху поиск ключа в словаре, маркер
|
|
123
|
+
* пропущенного ключа и откат на резервную локаль.
|
|
124
|
+
*
|
|
125
|
+
* @param locale - Локаль форматтера.
|
|
126
|
+
* @param options - Настройки; см. {@link MessageFormatOptions}.
|
|
127
|
+
* @returns Функция `(сообщение, значения) => строка`.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* const format = createMessageFormatter('ru');
|
|
132
|
+
* format('{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}', { count: 5 });
|
|
133
|
+
* // '5 файлов'
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export declare function createMessageFormatter(locale: string, options?: MessageFormatOptions): (source: string, values?: MessageValues) => string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { I18nHandle } from './translator';
|
|
2
|
+
/**
|
|
3
|
+
* Заменяет описатели сообщений на строки по активной локали. Обходит массивы и простые
|
|
4
|
+
* объекты-литералы — подписи опций, заголовки шагов; сигналы, React-элементы, функции и
|
|
5
|
+
* экземпляры классов (узлы формы) возвращает как есть. Поддерево без описателей сохраняет ссылку:
|
|
6
|
+
* если раскрывать было нечего, возвращается тот же объект.
|
|
7
|
+
*
|
|
8
|
+
* @param value - Пропсы, массив или одиночное значение.
|
|
9
|
+
* @param i18n - Ручка активной локали (`createI18n` / `useI18n`).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* resolveLocalized({ label: msg('profile.email.label'), options: [{ value: 'a', label: msg('opt.a') }] }, i18n);
|
|
14
|
+
* // { label: 'Почта', options: [{ value: 'a', label: 'Вариант А' }] }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveLocalized<T>(value: T, i18n: I18nHandle): T;
|