@pithy-sh/core 0.1.2 → 0.1.3
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/dist/address/address.d.ts +57 -0
- package/dist/address/address.d.ts.map +1 -0
- package/dist/address/address.js +71 -0
- package/dist/audit/auditEvent.d.ts +89 -0
- package/dist/audit/auditEvent.d.ts.map +1 -0
- package/dist/audit/auditEvent.js +69 -0
- package/dist/audit/recorder.d.ts +18 -0
- package/dist/audit/recorder.d.ts.map +1 -0
- package/dist/audit/recorder.js +8 -0
- package/dist/capability/bindings.d.ts +62 -0
- package/dist/capability/bindings.d.ts.map +1 -0
- package/dist/capability/bindings.js +97 -0
- package/dist/capability/capability.d.ts +501 -0
- package/dist/capability/capability.d.ts.map +1 -0
- package/dist/capability/capability.js +16 -0
- package/dist/capability/client.d.ts +70 -0
- package/dist/capability/client.d.ts.map +1 -0
- package/dist/capability/client.js +86 -0
- package/dist/capability/compose.d.ts +25 -0
- package/dist/capability/compose.d.ts.map +1 -0
- package/dist/capability/compose.js +54 -0
- package/dist/capability/composition.d.ts +26 -0
- package/dist/capability/composition.d.ts.map +1 -0
- package/dist/capability/composition.js +84 -0
- package/dist/capability/config.d.ts +7 -0
- package/dist/capability/config.d.ts.map +1 -0
- package/dist/capability/config.js +14 -0
- package/dist/capability/devSecret.d.ts +32 -0
- package/dist/capability/devSecret.d.ts.map +1 -0
- package/dist/capability/devSecret.js +28 -0
- package/dist/capability/manifest.d.ts +298 -0
- package/dist/capability/manifest.d.ts.map +1 -0
- package/dist/capability/manifest.js +356 -0
- package/dist/capability/secretOrigin.d.ts +121 -0
- package/dist/capability/secretOrigin.d.ts.map +1 -0
- package/dist/capability/secretOrigin.js +134 -0
- package/dist/capability/settings.d.ts +119 -0
- package/dist/capability/settings.d.ts.map +1 -0
- package/dist/capability/settings.js +66 -0
- package/dist/capability/validateBindings.d.ts +4 -0
- package/dist/capability/validateBindings.d.ts.map +1 -0
- package/dist/capability/validateBindings.js +34 -0
- package/dist/capability/vanishingKey.d.ts +31 -0
- package/dist/capability/vanishingKey.d.ts.map +1 -0
- package/dist/capability/vanishingKey.js +79 -0
- package/dist/controlPlane/audit/actions.d.ts +70 -0
- package/dist/controlPlane/audit/actions.d.ts.map +1 -0
- package/dist/controlPlane/audit/actions.js +76 -0
- package/dist/controlPlane/capability.d.ts +47 -0
- package/dist/controlPlane/capability.d.ts.map +1 -0
- package/dist/controlPlane/capability.js +138 -0
- package/dist/controlPlane/config/config.d.ts +19 -0
- package/dist/controlPlane/config/config.d.ts.map +1 -0
- package/dist/controlPlane/config/config.js +85 -0
- package/dist/controlPlane/context.d.ts +27 -0
- package/dist/controlPlane/context.d.ts.map +1 -0
- package/dist/controlPlane/context.js +29 -0
- package/dist/controlPlane/data/connection.d.ts +79 -0
- package/dist/controlPlane/data/connection.d.ts.map +1 -0
- package/dist/controlPlane/data/connection.js +55 -0
- package/dist/controlPlane/data/keyLifecycle.d.ts +62 -0
- package/dist/controlPlane/data/keyLifecycle.d.ts.map +1 -0
- package/dist/controlPlane/data/keyLifecycle.js +126 -0
- package/dist/controlPlane/data/replay.d.ts +18 -0
- package/dist/controlPlane/data/replay.d.ts.map +1 -0
- package/dist/controlPlane/data/replay.js +20 -0
- package/dist/controlPlane/data/tables.d.ts +34 -0
- package/dist/controlPlane/data/tables.d.ts.map +1 -0
- package/dist/controlPlane/data/tables.js +37 -0
- package/dist/controlPlane/discovery/adminRoute.d.ts +313 -0
- package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -0
- package/dist/controlPlane/discovery/adminRoute.js +136 -0
- package/dist/controlPlane/discovery/configuration.d.ts +108 -0
- package/dist/controlPlane/discovery/configuration.d.ts.map +1 -0
- package/dist/controlPlane/discovery/configuration.js +187 -0
- package/dist/controlPlane/discovery/drift.d.ts +64 -0
- package/dist/controlPlane/discovery/drift.d.ts.map +1 -0
- package/dist/controlPlane/discovery/drift.js +58 -0
- package/dist/controlPlane/discovery/health.d.ts +99 -0
- package/dist/controlPlane/discovery/health.d.ts.map +1 -0
- package/dist/controlPlane/discovery/health.js +151 -0
- package/dist/controlPlane/discovery/healthSummary.d.ts +236 -0
- package/dist/controlPlane/discovery/healthSummary.d.ts.map +1 -0
- package/dist/controlPlane/discovery/healthSummary.js +278 -0
- package/dist/controlPlane/error/errors.d.ts +67 -0
- package/dist/controlPlane/error/errors.d.ts.map +1 -0
- package/dist/controlPlane/error/errors.js +79 -0
- package/dist/controlPlane/http/cors.d.ts +112 -0
- package/dist/controlPlane/http/cors.d.ts.map +1 -0
- package/dist/controlPlane/http/cors.js +192 -0
- package/dist/controlPlane/http/guard.d.ts +77 -0
- package/dist/controlPlane/http/guard.d.ts.map +1 -0
- package/dist/controlPlane/http/guard.js +142 -0
- package/dist/controlPlane/http/handlers.d.ts +179 -0
- package/dist/controlPlane/http/handlers.d.ts.map +1 -0
- package/dist/controlPlane/http/handlers.js +245 -0
- package/dist/controlPlane/http/responses.d.ts +99 -0
- package/dist/controlPlane/http/responses.d.ts.map +1 -0
- package/dist/controlPlane/http/responses.js +63 -0
- package/dist/controlPlane/http/routes.d.ts +40 -0
- package/dist/controlPlane/http/routes.d.ts.map +1 -0
- package/dist/controlPlane/http/routes.js +83 -0
- package/dist/controlPlane/http/schemas.d.ts +39 -0
- package/dist/controlPlane/http/schemas.d.ts.map +1 -0
- package/dist/controlPlane/http/schemas.js +31 -0
- package/dist/controlPlane/http/verify.d.ts +84 -0
- package/dist/controlPlane/http/verify.d.ts.map +1 -0
- package/dist/controlPlane/http/verify.js +65 -0
- package/dist/controlPlane/migrations/0001_init.d.ts +51 -0
- package/dist/controlPlane/migrations/0001_init.d.ts.map +1 -0
- package/dist/controlPlane/migrations/0001_init.js +65 -0
- package/dist/controlPlane/replay/d1Guard.d.ts +29 -0
- package/dist/controlPlane/replay/d1Guard.d.ts.map +1 -0
- package/dist/controlPlane/replay/d1Guard.js +39 -0
- package/dist/controlPlane/replay/guard.d.ts +52 -0
- package/dist/controlPlane/replay/guard.d.ts.map +1 -0
- package/dist/controlPlane/replay/guard.js +0 -0
- package/dist/controlPlane/replay/kvGuard.d.ts +99 -0
- package/dist/controlPlane/replay/kvGuard.d.ts.map +1 -0
- package/dist/controlPlane/replay/kvGuard.js +101 -0
- package/dist/controlPlane/scope/scope.d.ts +68 -0
- package/dist/controlPlane/scope/scope.d.ts.map +1 -0
- package/dist/controlPlane/scope/scope.js +63 -0
- package/dist/controlPlane/token/base64url.d.ts +10 -0
- package/dist/controlPlane/token/base64url.d.ts.map +1 -0
- package/dist/controlPlane/token/base64url.js +48 -0
- package/dist/controlPlane/token/claims.d.ts +83 -0
- package/dist/controlPlane/token/claims.d.ts.map +1 -0
- package/dist/controlPlane/token/claims.js +86 -0
- package/dist/controlPlane/token/digest.d.ts +44 -0
- package/dist/controlPlane/token/digest.d.ts.map +1 -0
- package/dist/controlPlane/token/digest.js +56 -0
- package/dist/controlPlane/token/jws.d.ts +52 -0
- package/dist/controlPlane/token/jws.d.ts.map +1 -0
- package/dist/controlPlane/token/jws.js +65 -0
- package/dist/controlPlane/token/mint.d.ts +56 -0
- package/dist/controlPlane/token/mint.d.ts.map +1 -0
- package/dist/controlPlane/token/mint.js +55 -0
- package/dist/controlPlane/wire.d.ts +122 -0
- package/dist/controlPlane/wire.d.ts.map +1 -0
- package/dist/controlPlane/wire.js +123 -0
- package/dist/createBackend.d.ts +58 -0
- package/dist/createBackend.d.ts.map +1 -0
- package/dist/createBackend.js +170 -0
- package/dist/createEntrypoint.d.ts +53 -0
- package/dist/createEntrypoint.d.ts.map +1 -0
- package/dist/createEntrypoint.js +52 -0
- package/dist/data/boundParameters.d.ts +83 -0
- package/dist/data/boundParameters.d.ts.map +1 -0
- package/dist/data/boundParameters.js +162 -0
- package/dist/data/codecs.d.ts +34 -0
- package/dist/data/codecs.d.ts.map +1 -0
- package/dist/data/codecs.js +135 -0
- package/dist/data/cursor.d.ts +81 -0
- package/dist/data/cursor.d.ts.map +1 -0
- package/dist/data/cursor.js +104 -0
- package/dist/data/databases.d.ts +55 -0
- package/dist/data/databases.d.ts.map +1 -0
- package/dist/data/databases.js +32 -0
- package/dist/data/db.d.ts +42 -0
- package/dist/data/db.d.ts.map +1 -0
- package/dist/data/db.js +24 -0
- package/dist/data/withD1Retry.d.ts +91 -0
- package/dist/data/withD1Retry.d.ts.map +1 -0
- package/dist/data/withD1Retry.js +152 -0
- package/dist/entitlement/entitlement.d.ts +118 -0
- package/dist/entitlement/entitlement.d.ts.map +1 -0
- package/dist/entitlement/entitlement.js +59 -0
- package/dist/entitlement/gateScan.d.ts +26 -0
- package/dist/entitlement/gateScan.d.ts.map +1 -0
- package/dist/entitlement/gateScan.js +96 -0
- package/dist/entitlement/require.d.ts +12 -0
- package/dist/entitlement/require.d.ts.map +1 -0
- package/dist/entitlement/require.js +123 -0
- package/dist/env/ambient.d.ts +52 -0
- package/dist/env/ambient.d.ts.map +1 -0
- package/dist/env/ambient.js +35 -0
- package/dist/env/ci.d.ts +36 -0
- package/dist/env/ci.d.ts.map +1 -0
- package/dist/env/ci.js +40 -0
- package/dist/env/stem.d.ts +23 -0
- package/dist/env/stem.d.ts.map +1 -0
- package/dist/env/stem.js +29 -0
- package/dist/error/cause.d.ts +143 -0
- package/dist/error/cause.d.ts.map +1 -0
- package/dist/error/cause.js +192 -0
- package/dist/error/client.d.ts +35 -0
- package/dist/error/client.d.ts.map +1 -0
- package/dist/error/client.js +38 -0
- package/dist/error/extend.d.ts +101 -0
- package/dist/error/extend.d.ts.map +1 -0
- package/dist/error/extend.js +76 -0
- package/dist/error/http.d.ts +1516 -0
- package/dist/error/http.d.ts.map +1 -0
- package/dist/error/http.js +72 -0
- package/dist/error/payload.d.ts +3034 -0
- package/dist/error/payload.d.ts.map +1 -0
- package/dist/error/payload.js +1110 -0
- package/dist/error/pithyError.d.ts +154 -0
- package/dist/error/pithyError.d.ts.map +1 -0
- package/dist/error/pithyError.js +197 -0
- package/dist/error/terminal.d.ts +28 -0
- package/dist/error/terminal.d.ts.map +1 -0
- package/dist/error/terminal.js +33 -0
- package/dist/http/authContext.d.ts +21 -0
- package/dist/http/authContext.d.ts.map +1 -0
- package/dist/http/authContext.js +23 -0
- package/dist/http/routeContract.d.ts +39 -0
- package/dist/http/routeContract.d.ts.map +1 -0
- package/dist/http/routeContract.js +82 -0
- package/dist/http/sameOrigin.d.ts +47 -0
- package/dist/http/sameOrigin.d.ts.map +1 -0
- package/dist/http/sameOrigin.js +24 -0
- package/dist/http/signedWebhook.d.ts +225 -0
- package/dist/http/signedWebhook.d.ts.map +1 -0
- package/dist/http/signedWebhook.js +247 -0
- package/dist/http/validation.d.ts +37 -0
- package/dist/http/validation.d.ts.map +1 -0
- package/dist/http/validation.js +14 -0
- package/dist/http/verification.d.ts +10 -0
- package/dist/http/verification.d.ts.map +1 -0
- package/dist/http/verification.js +17 -0
- package/dist/i18n/acceptLanguage.d.ts +22 -0
- package/dist/i18n/acceptLanguage.d.ts.map +1 -0
- package/dist/i18n/acceptLanguage.js +55 -0
- package/dist/i18n/catalog.d.ts +39 -0
- package/dist/i18n/catalog.d.ts.map +1 -0
- package/dist/i18n/catalog.js +57 -0
- package/dist/i18n/locale.d.ts +74 -0
- package/dist/i18n/locale.d.ts.map +1 -0
- package/dist/i18n/locale.js +109 -0
- package/dist/i18n/localeMarker.d.ts +29 -0
- package/dist/i18n/localeMarker.d.ts.map +1 -0
- package/dist/i18n/localeMarker.js +112 -0
- package/dist/i18n/match.d.ts +26 -0
- package/dist/i18n/match.d.ts.map +1 -0
- package/dist/i18n/match.js +88 -0
- package/dist/i18n/registry.d.ts +25 -0
- package/dist/i18n/registry.d.ts.map +1 -0
- package/dist/i18n/registry.js +51 -0
- package/dist/i18n/translator.d.ts +107 -0
- package/dist/i18n/translator.d.ts.map +1 -0
- package/dist/i18n/translator.js +80 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/kv/kv.d.ts +166 -0
- package/dist/kv/kv.d.ts.map +1 -0
- package/dist/kv/kv.js +261 -0
- package/dist/kv/namespaces.d.ts +58 -0
- package/dist/kv/namespaces.d.ts.map +1 -0
- package/dist/kv/namespaces.js +40 -0
- package/dist/logger/local.d.ts +41 -0
- package/dist/logger/local.d.ts.map +1 -0
- package/dist/logger/local.js +45 -0
- package/dist/logger/logger.d.ts +56 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +83 -0
- package/dist/logger/record.d.ts +919 -0
- package/dist/logger/record.d.ts.map +1 -0
- package/dist/logger/record.js +79 -0
- package/dist/logger/worker.d.ts +74 -0
- package/dist/logger/worker.d.ts.map +1 -0
- package/dist/logger/worker.js +54 -0
- package/dist/migrations/batch.d.ts +10 -0
- package/dist/migrations/batch.d.ts.map +1 -0
- package/dist/migrations/batch.js +193 -0
- package/dist/migrations/bookkeeping.d.ts +24 -0
- package/dist/migrations/bookkeeping.d.ts.map +1 -0
- package/dist/migrations/bookkeeping.js +57 -0
- package/dist/migrations/owner.d.ts +41 -0
- package/dist/migrations/owner.d.ts.map +1 -0
- package/dist/migrations/owner.js +105 -0
- package/dist/migrations/registry.d.ts +36 -0
- package/dist/migrations/registry.d.ts.map +1 -0
- package/dist/migrations/registry.js +70 -0
- package/dist/migrations/runner.d.ts +67 -0
- package/dist/migrations/runner.d.ts.map +1 -0
- package/dist/migrations/runner.js +218 -0
- package/dist/naming/domains.d.ts +108 -0
- package/dist/naming/domains.d.ts.map +1 -0
- package/dist/naming/domains.js +156 -0
- package/dist/naming/environment.d.ts +118 -0
- package/dist/naming/environment.d.ts.map +1 -0
- package/dist/naming/environment.js +190 -0
- package/dist/naming/feature.d.ts +68 -0
- package/dist/naming/feature.d.ts.map +1 -0
- package/dist/naming/feature.js +106 -0
- package/dist/naming/limits.d.ts +229 -0
- package/dist/naming/limits.d.ts.map +1 -0
- package/dist/naming/limits.js +187 -0
- package/dist/naming/provisionScope.d.ts +91 -0
- package/dist/naming/provisionScope.d.ts.map +1 -0
- package/dist/naming/provisionScope.js +65 -0
- package/dist/naming/resource.d.ts +169 -0
- package/dist/naming/resource.d.ts.map +1 -0
- package/dist/naming/resource.js +220 -0
- package/dist/naming/resourceNames.d.ts +83 -0
- package/dist/naming/resourceNames.d.ts.map +1 -0
- package/dist/naming/resourceNames.js +102 -0
- package/dist/naming/segment.d.ts +29 -0
- package/dist/naming/segment.d.ts.map +1 -0
- package/dist/naming/segment.js +31 -0
- package/dist/projection/asRead.d.ts +72 -0
- package/dist/projection/asRead.d.ts.map +1 -0
- package/dist/projection/asRead.js +169 -0
- package/dist/projection/published.d.ts +95 -0
- package/dist/projection/published.d.ts.map +1 -0
- package/dist/projection/published.js +114 -0
- package/dist/schema/describedness.d.ts +55 -0
- package/dist/schema/describedness.d.ts.map +1 -0
- package/dist/schema/describedness.js +180 -0
- package/dist/seed/compose.d.ts +41 -0
- package/dist/seed/compose.d.ts.map +1 -0
- package/dist/seed/compose.js +51 -0
- package/dist/seed/devLogin.d.ts +50 -0
- package/dist/seed/devLogin.d.ts.map +1 -0
- package/dist/seed/devLogin.js +52 -0
- package/dist/seed/exampleIdentities.d.ts +58 -0
- package/dist/seed/exampleIdentities.d.ts.map +1 -0
- package/dist/seed/exampleIdentities.js +26 -0
- package/dist/seed/metadata.d.ts +20 -0
- package/dist/seed/metadata.d.ts.map +1 -0
- package/dist/seed/metadata.js +27 -0
- package/dist/seed/seed.d.ts +264 -0
- package/dist/seed/seed.d.ts.map +1 -0
- package/dist/seed/seed.js +37 -0
- package/dist/seed/seededRows.d.ts +26 -0
- package/dist/seed/seededRows.d.ts.map +1 -0
- package/dist/seed/seededRows.js +20 -0
- package/dist/seed/writeD1.d.ts +43 -0
- package/dist/seed/writeD1.d.ts.map +1 -0
- package/dist/seed/writeD1.js +73 -0
- package/dist/seed/writeKv.d.ts +29 -0
- package/dist/seed/writeKv.d.ts.map +1 -0
- package/dist/seed/writeKv.js +63 -0
- package/dist/semver/semver.d.ts +62 -0
- package/dist/semver/semver.d.ts.map +1 -0
- package/dist/semver/semver.js +137 -0
- package/dist/text/comments.d.ts +62 -0
- package/dist/text/comments.d.ts.map +1 -0
- package/dist/text/comments.js +161 -0
- package/dist/version.generated.d.ts +5 -0
- package/dist/version.generated.d.ts.map +1 -0
- package/dist/version.generated.js +7 -0
- package/dist/worker/health.d.ts +39 -0
- package/dist/worker/health.d.ts.map +1 -0
- package/dist/worker/health.js +41 -0
- package/dist/worker/identity.d.ts +191 -0
- package/dist/worker/identity.d.ts.map +1 -0
- package/dist/worker/identity.js +162 -0
- package/dist/workflow/bindings.d.ts +41 -0
- package/dist/workflow/bindings.d.ts.map +1 -0
- package/dist/workflow/bindings.js +53 -0
- package/dist/workflow/dispatch.d.ts +98 -0
- package/dist/workflow/dispatch.d.ts.map +1 -0
- package/dist/workflow/dispatch.js +148 -0
- package/dist/workflow/dispatchRoute.d.ts +80 -0
- package/dist/workflow/dispatchRoute.d.ts.map +1 -0
- package/dist/workflow/dispatchRoute.js +134 -0
- package/dist/workflow/faults.d.ts +97 -0
- package/dist/workflow/faults.d.ts.map +1 -0
- package/dist/workflow/faults.js +111 -0
- package/dist/workflow/host.d.ts +176 -0
- package/dist/workflow/host.d.ts.map +1 -0
- package/dist/workflow/host.js +116 -0
- package/dist/workflow/hostEntry.d.ts +50 -0
- package/dist/workflow/hostEntry.d.ts.map +1 -0
- package/dist/workflow/hostEntry.js +24 -0
- package/dist/workflow/hostEnv.d.ts +143 -0
- package/dist/workflow/hostEnv.d.ts.map +1 -0
- package/dist/workflow/hostEnv.js +170 -0
- package/dist/workflow/loopback.d.ts +52 -0
- package/dist/workflow/loopback.d.ts.map +1 -0
- package/dist/workflow/loopback.js +76 -0
- package/dist/workflow/naming.d.ts +79 -0
- package/dist/workflow/naming.d.ts.map +1 -0
- package/dist/workflow/naming.js +138 -0
- package/dist/workflow/register.d.ts +19 -0
- package/dist/workflow/register.d.ts.map +1 -0
- package/dist/workflow/register.js +40 -0
- package/dist/workflow/schemas.d.ts +44 -0
- package/dist/workflow/schemas.d.ts.map +1 -0
- package/dist/workflow/schemas.js +43 -0
- package/dist/workflow/spec.d.ts +81 -0
- package/dist/workflow/spec.d.ts.map +1 -0
- package/dist/workflow/spec.js +0 -0
- package/dist/workflow/stepMessage.d.ts +122 -0
- package/dist/workflow/stepMessage.d.ts.map +1 -0
- package/dist/workflow/stepMessage.js +152 -0
- package/package.json +20 -9
- package/src/capability/manifest.ts +21 -0
- package/src/version.generated.ts +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import "./locale.js";
|
|
2
|
+
//#region src/i18n/acceptLanguage.ts
|
|
3
|
+
/**
|
|
4
|
+
* The most `Accept-Language` entries this parser will read from one header.
|
|
5
|
+
*
|
|
6
|
+
* A browser sends a handful. A caller that sends ten thousand is not negotiating, and the matcher
|
|
7
|
+
* walks every entry against every supported locale — so the bound is on the input, where it costs
|
|
8
|
+
* nothing, rather than on the loop, where it would have to be argued about at each call site.
|
|
9
|
+
*/
|
|
10
|
+
const MAX_ACCEPT_LANGUAGE_ENTRIES = 32;
|
|
11
|
+
/** The most characters this parser will read from one `Accept-Language` header, for the same reason. */
|
|
12
|
+
const MAX_ACCEPT_LANGUAGE_LENGTH = 4096;
|
|
13
|
+
/** Whether a q-weight parameter parses to a usable number. `q=` and `q=abc` are neither, and drop out. */
|
|
14
|
+
function quality(parameter) {
|
|
15
|
+
const match = /^q=(\d(?:\.\d{0,3})?)$/.exec(parameter);
|
|
16
|
+
if (!match?.[1]) return null;
|
|
17
|
+
const value = Number.parseFloat(match[1]);
|
|
18
|
+
return Number.isFinite(value) && value >= 0 && value <= 1 ? value : null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* An `Accept-Language` header as q-weighted ranges, most-wanted first.
|
|
22
|
+
*
|
|
23
|
+
* **Tokenize, strip q-weights, then validate — in that order, and all of it before anything reaches
|
|
24
|
+
* `Intl`.** The header is caller-supplied and its real-world contents include `*`, `en_US`, empty
|
|
25
|
+
* tokens, and tags still carrying `;q=0.9`; `new Intl.Locale()` throws a `RangeError` on every one of
|
|
26
|
+
* them. A header that cannot be read is an empty list, never a throw, so the resolver chain falls
|
|
27
|
+
* through to its next link instead of 500-ing.
|
|
28
|
+
*
|
|
29
|
+
* The **full** weighted list is returned, not the first entry: `pt-PT;q=1.0, es;q=0.8, en;q=0.5` from a
|
|
30
|
+
* reader who has no Portuguese is a request for Spanish, and reading only the head answers English.
|
|
31
|
+
* Equal weights keep header order, which is what a browser means by listing them that way.
|
|
32
|
+
*/
|
|
33
|
+
function parseAcceptLanguage(header) {
|
|
34
|
+
if (!header || header.length > MAX_ACCEPT_LANGUAGE_LENGTH) return [];
|
|
35
|
+
const ranges = [];
|
|
36
|
+
for (const entry of header.split(",")) {
|
|
37
|
+
if (ranges.length >= MAX_ACCEPT_LANGUAGE_ENTRIES) break;
|
|
38
|
+
const [rawRange, ...parameters] = entry.split(";");
|
|
39
|
+
const range = rawRange?.trim().toLowerCase() ?? "";
|
|
40
|
+
if (range !== "*" && !/^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/.test(range)) continue;
|
|
41
|
+
if (range.length > 64) continue;
|
|
42
|
+
let weight = 1;
|
|
43
|
+
for (const parameter of parameters) {
|
|
44
|
+
const parsed = quality(parameter.trim().toLowerCase());
|
|
45
|
+
if (parsed !== null) weight = parsed;
|
|
46
|
+
}
|
|
47
|
+
if (weight > 0) ranges.push({
|
|
48
|
+
range,
|
|
49
|
+
quality: weight
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return ranges.sort((left, right) => right.quality - left.quality);
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { parseAcceptLanguage };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** A catalog key — `<domain>/<path>`, where `<domain>` is the contributing capability's name. */
|
|
3
|
+
export declare const MessageKey: z.ZodString;
|
|
4
|
+
export type MessageKey = z.infer<typeof MessageKey>;
|
|
5
|
+
/** The values a message may interpolate. Scalars only — a catalog renders text, never a structure. */
|
|
6
|
+
export declare const MessageParams: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
7
|
+
export type MessageParams = z.infer<typeof MessageParams>;
|
|
8
|
+
/** One locale's words: key → message. */
|
|
9
|
+
export declare const MessageCatalog: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
10
|
+
export type MessageCatalog = z.infer<typeof MessageCatalog>;
|
|
11
|
+
/** A set of catalogs, keyed by locale — what a capability contributes and what an adopter overrides with. */
|
|
12
|
+
export declare const LocaleCatalogs: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
13
|
+
export type LocaleCatalogs = z.infer<typeof LocaleCatalogs>;
|
|
14
|
+
/** The domain of a catalog key — everything before the `/`. */
|
|
15
|
+
export declare function messageDomain(key: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* `template` with every `{placeholder}` replaced by its parameter.
|
|
18
|
+
*
|
|
19
|
+
* A placeholder with no parameter is **left as written**, not blanked. A missing value is a bug in the
|
|
20
|
+
* call site or a typo in the catalog, and `Renews {date}.` on the screen says which; `Renews .` says
|
|
21
|
+
* nothing and reads like finished copy.
|
|
22
|
+
*
|
|
23
|
+
* Substitution is textual and performs no escaping, because the two consumers escape differently and
|
|
24
|
+
* both do it better than this could: React escapes what it renders, and the email engine precompiles
|
|
25
|
+
* `subject` and `text` with `noEscape` deliberately. A catalog value reaching an unescaped surface is
|
|
26
|
+
* therefore a fact about that surface — stated in `docs/I18N.md`, and the reason kit catalogs carry no
|
|
27
|
+
* markup.
|
|
28
|
+
*/
|
|
29
|
+
export declare function interpolate(template: string, params?: MessageParams): string;
|
|
30
|
+
/**
|
|
31
|
+
* The first layer that has `key`, or `null` when none does.
|
|
32
|
+
*
|
|
33
|
+
* **Per key, never per catalog.** An adopter who translates one sentence passes one entry, and every
|
|
34
|
+
* key they did not mention keeps flowing from the package — which is what makes an override a merge
|
|
35
|
+
* rather than a fork. Layer order is the caller's: `@pithy-sh/i18n` walks adopter-locale, then
|
|
36
|
+
* adopter-default, then kit-locale, then kit-default.
|
|
37
|
+
*/
|
|
38
|
+
export declare function lookupMessage(layers: readonly (MessageCatalog | undefined)[], key: string): string | null;
|
|
39
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/i18n/catalog.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA2BxB,iGAAiG;AACjG,eAAO,MAAM,UAAU,aAI+D,CAAC;AACvF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,sGAAsG;AACtG,eAAO,MAAM,aAAa,yFAO8C,CAAC;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,yCAAyC;AACzC,eAAO,MAAM,cAAc,uCAEsC,CAAC;AAClE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,6GAA6G;AAC7G,eAAO,MAAM,cAAc,iEAEiF,CAAC;AAC7G,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAW5E;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,cAAc,GAAG,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWzG"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Locale } from "./locale.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/** A catalog key — `<domain>/<path>`, where `<domain>` is the contributing capability's name. */
|
|
4
|
+
const MessageKey = z.string().max(129).regex(/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*\/[a-z0-9]+(?:_[a-z0-9]+)*(?:\.[a-z0-9]+(?:_[a-z0-9]+)*)*$/, { message: "A message key is `<domain>/<path>`, where the domain is a capability name." }).describe("A catalog key, `<domain>/<path>` — for an error, the error code itself.");
|
|
5
|
+
/** The values a message may interpolate. Scalars only — a catalog renders text, never a structure. */
|
|
6
|
+
const MessageParams = z.record(z.string().describe("The placeholder name, as written between braces in the message."), z.union([
|
|
7
|
+
z.string(),
|
|
8
|
+
z.number(),
|
|
9
|
+
z.boolean()
|
|
10
|
+
]).describe("The value substituted for that placeholder. Scalar, so a message stays a sentence.")).describe("Values a message interpolates, keyed by placeholder name.");
|
|
11
|
+
/** One locale's words: key → message. */
|
|
12
|
+
const MessageCatalog = z.record(MessageKey.describe("The key this message answers."), z.string().describe("The message, in this locale.")).describe("One locale's messages, keyed by `<domain>/<path>`.");
|
|
13
|
+
/** A set of catalogs, keyed by locale — what a capability contributes and what an adopter overrides with. */
|
|
14
|
+
const LocaleCatalogs = z.record(Locale.describe("The locale these messages are written in."), MessageCatalog).describe("Catalogs keyed by locale — a capability's `messages` contribution, or an adopter's overrides.");
|
|
15
|
+
/** The domain of a catalog key — everything before the `/`. */
|
|
16
|
+
function messageDomain(key) {
|
|
17
|
+
const cut = key.indexOf("/");
|
|
18
|
+
return cut < 0 ? key : key.slice(0, cut);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `template` with every `{placeholder}` replaced by its parameter.
|
|
22
|
+
*
|
|
23
|
+
* A placeholder with no parameter is **left as written**, not blanked. A missing value is a bug in the
|
|
24
|
+
* call site or a typo in the catalog, and `Renews {date}.` on the screen says which; `Renews .` says
|
|
25
|
+
* nothing and reads like finished copy.
|
|
26
|
+
*
|
|
27
|
+
* Substitution is textual and performs no escaping, because the two consumers escape differently and
|
|
28
|
+
* both do it better than this could: React escapes what it renders, and the email engine precompiles
|
|
29
|
+
* `subject` and `text` with `noEscape` deliberately. A catalog value reaching an unescaped surface is
|
|
30
|
+
* therefore a fact about that surface — stated in `docs/I18N.md`, and the reason kit catalogs carry no
|
|
31
|
+
* markup.
|
|
32
|
+
*/
|
|
33
|
+
function interpolate(template, params) {
|
|
34
|
+
if (!params) return template;
|
|
35
|
+
return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (whole, name) => {
|
|
36
|
+
if (!Object.hasOwn(params, name)) return whole;
|
|
37
|
+
const value = params[name];
|
|
38
|
+
return value === void 0 ? whole : String(value);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The first layer that has `key`, or `null` when none does.
|
|
43
|
+
*
|
|
44
|
+
* **Per key, never per catalog.** An adopter who translates one sentence passes one entry, and every
|
|
45
|
+
* key they did not mention keeps flowing from the package — which is what makes an override a merge
|
|
46
|
+
* rather than a fork. Layer order is the caller's: `@pithy-sh/i18n` walks adopter-locale, then
|
|
47
|
+
* adopter-default, then kit-locale, then kit-default.
|
|
48
|
+
*/
|
|
49
|
+
function lookupMessage(layers, key) {
|
|
50
|
+
for (const layer of layers) if (layer && Object.hasOwn(layer, key)) {
|
|
51
|
+
const message = layer[key];
|
|
52
|
+
if (message !== void 0) return message;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { LocaleCatalogs, MessageCatalog, MessageKey, MessageParams, interpolate, lookupMessage, messageDomain };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The longest language tag this kit accepts. RFC 5646's own grammar allows far more, but a tag that
|
|
4
|
+
* long is not a locale anyone serves — it is a header a caller filled with something else. The bound
|
|
5
|
+
* exists so a hostile `Accept-Language` cannot make the matcher walk a megabyte per request.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MAX_LOCALE_TAG_LENGTH = 64;
|
|
8
|
+
/**
|
|
9
|
+
* A BCP-47 language tag — the identity of a locale everywhere in the kit.
|
|
10
|
+
*
|
|
11
|
+
* One string, two jobs, and only one of them falls back: the tag that picks a **catalog** is the words
|
|
12
|
+
* we have written, and the tag that drives **formatting** is whatever the reader asked for. See
|
|
13
|
+
* {@link LocaleContext} for why collapsing them is the bug where an Argentine reads Spanish and sees
|
|
14
|
+
* `1,234.56`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const Locale: z.ZodString;
|
|
17
|
+
export type Locale = z.infer<typeof Locale>;
|
|
18
|
+
/** Which way a locale's script runs. Drives the `dir` attribute on the document and the email shell. */
|
|
19
|
+
export declare const TextDirection: z.ZodEnum<{
|
|
20
|
+
ltr: "ltr";
|
|
21
|
+
rtl: "rtl";
|
|
22
|
+
}>;
|
|
23
|
+
export type TextDirection = z.infer<typeof TextDirection>;
|
|
24
|
+
/**
|
|
25
|
+
* The per-request locale seam, published on `c.var.locale` by `@pithy-sh/i18n` and `null` in a Worker
|
|
26
|
+
* that does not compose it. Core defines the shape; one capability populates it — the same split
|
|
27
|
+
* `AuthContext` uses, and for the same reason: `@pithy-sh/email` and the error module both need a
|
|
28
|
+
* locale and neither may import an i18n capability.
|
|
29
|
+
*/
|
|
30
|
+
export declare const LocaleContext: z.ZodObject<{
|
|
31
|
+
catalogLocale: z.ZodString;
|
|
32
|
+
formattingLocale: z.ZodString;
|
|
33
|
+
direction: z.ZodEnum<{
|
|
34
|
+
ltr: "ltr";
|
|
35
|
+
rtl: "rtl";
|
|
36
|
+
}>;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
export type LocaleContext = z.infer<typeof LocaleContext>;
|
|
39
|
+
/**
|
|
40
|
+
* `tag` as an `Intl.Locale`, or `null` when it is not one.
|
|
41
|
+
*
|
|
42
|
+
* **Every construction on header- or adopter-supplied input goes through this.** `new Intl.Locale()`
|
|
43
|
+
* throws `RangeError` on `*`, on `en_US`, on the empty string, and on any token still carrying
|
|
44
|
+
* `;q=0.9` — so a malformed `Accept-Language` is an uncaught 500 rather than a fallback, unless the
|
|
45
|
+
* throw is caught somewhere. It is caught here, once, and nowhere else.
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseLocale(tag: string): Intl.Locale | null;
|
|
48
|
+
/**
|
|
49
|
+
* The part of `tag` that may be handed to `Intl` as a **formatting** locale, or `null` when it is not a
|
|
50
|
+
* tag at all: language, script, region and variants, with every extension subtag stripped.
|
|
51
|
+
*
|
|
52
|
+
* **A `-u-` extension is not a locale, it is an instruction**, and the range a formatting locale is
|
|
53
|
+
* built from is caller-supplied. `?lang=en-u-nu-hanidec` truncates to `en`, matches a project that
|
|
54
|
+
* ships English, and — kept whole — reaches `Intl.NumberFormat` verbatim, where it renders `1,234` as
|
|
55
|
+
* `一,二三四`. `-u-ca-islamic` does the same to every date on the page. Nothing is disclosed and nothing
|
|
56
|
+
* persists, so it is spoofing rather than a breach; it is also a link somebody can send, and the whole
|
|
57
|
+
* point of resolving against `supportedLocales` is that a reader cannot pick a rendering the project
|
|
58
|
+
* did not offer.
|
|
59
|
+
*
|
|
60
|
+
* `baseName` is exactly the right cut, and is why this is a helper rather than a regex: `es-AR`,
|
|
61
|
+
* `zh-Hant-TW`, `es-419` and `de-DE-1996` all survive whole, because none of them is an extension.
|
|
62
|
+
*/
|
|
63
|
+
export declare function formattingLocaleOf(tag: string): string | null;
|
|
64
|
+
/** Whether `tag` is a language tag `Intl` will accept — the predicate half of {@link parseLocale}. */
|
|
65
|
+
export declare function isLocale(tag: string): tag is Locale;
|
|
66
|
+
/**
|
|
67
|
+
* Which way `tag`'s script runs, feature-detected across both spellings and falling back to a small
|
|
68
|
+
* right-to-left language set when a runtime exposes neither.
|
|
69
|
+
*
|
|
70
|
+
* Total: an unparseable tag is `ltr`, because a document still needs a `dir` and guessing the other way
|
|
71
|
+
* mirrors a page nobody asked to mirror.
|
|
72
|
+
*/
|
|
73
|
+
export declare function localeDirection(tag: string): TextDirection;
|
|
74
|
+
//# sourceMappingURL=locale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../src/i18n/locale.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAaxC;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,aAoBkF,CAAC;AACtG,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAE5C,wGAAwG;AACxG,eAAO,MAAM,aAAa;;;EAEyD,CAAC;AACpF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;;;;iBAM8E,CAAC;AACzG,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAO3D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7D;AAED,sGAAsG;AACtG,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,MAAM,CAEnD;AAuBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAQ1D"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/i18n/locale.ts
|
|
3
|
+
/**
|
|
4
|
+
* The longest language tag this kit accepts. RFC 5646's own grammar allows far more, but a tag that
|
|
5
|
+
* long is not a locale anyone serves — it is a header a caller filled with something else. The bound
|
|
6
|
+
* exists so a hostile `Accept-Language` cannot make the matcher walk a megabyte per request.
|
|
7
|
+
*/
|
|
8
|
+
const MAX_LOCALE_TAG_LENGTH = 64;
|
|
9
|
+
/**
|
|
10
|
+
* The shape of a well-formed language tag, checked before `Intl.Locale` ever sees it.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately looser than RFC 5646 and deliberately stricter than "any string". Looser, because the
|
|
13
|
+
* authority on whether `es-419` or `zh-Hans-CN` is meaningful is ICU, not a regular expression we would
|
|
14
|
+
* have to keep in step with it. Stricter, because {@link https://tc39.es/ecma402 `Intl.Locale`} throws a
|
|
15
|
+
* `RangeError` rather than returning a signal, and the four things it throws on — `*`, `en_US`, the empty
|
|
16
|
+
* string, and a token still carrying `;q=0.9` — all appear in real `Accept-Language` headers.
|
|
17
|
+
*/
|
|
18
|
+
const LANGUAGE_TAG = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/;
|
|
19
|
+
/**
|
|
20
|
+
* A BCP-47 language tag — the identity of a locale everywhere in the kit.
|
|
21
|
+
*
|
|
22
|
+
* One string, two jobs, and only one of them falls back: the tag that picks a **catalog** is the words
|
|
23
|
+
* we have written, and the tag that drives **formatting** is whatever the reader asked for. See
|
|
24
|
+
* {@link LocaleContext} for why collapsing them is the bug where an Argentine reads Spanish and sees
|
|
25
|
+
* `1,234.56`.
|
|
26
|
+
*/
|
|
27
|
+
const Locale = z.string().min(2).max(64).regex(LANGUAGE_TAG, { message: "A locale is a BCP-47 tag like `en`, `es`, or `es-AR`." }).refine((tag) => parseLocale(tag) !== null, { message: "That is not a language tag `Intl` accepts. `en-x` and `en-a-bbb-a-ccc` are the usual shapes that miss." }).describe("A BCP-47 language tag (`en`, `es`, `es-AR`) — the identity of a locale across the kit.");
|
|
28
|
+
/** Which way a locale's script runs. Drives the `dir` attribute on the document and the email shell. */
|
|
29
|
+
const TextDirection = z.enum(["ltr", "rtl"]).describe("Which way a locale's script runs — the value of the `dir` attribute.");
|
|
30
|
+
/**
|
|
31
|
+
* The per-request locale seam, published on `c.var.locale` by `@pithy-sh/i18n` and `null` in a Worker
|
|
32
|
+
* that does not compose it. Core defines the shape; one capability populates it — the same split
|
|
33
|
+
* `AuthContext` uses, and for the same reason: `@pithy-sh/email` and the error module both need a
|
|
34
|
+
* locale and neither may import an i18n capability.
|
|
35
|
+
*/
|
|
36
|
+
const LocaleContext = z.object({
|
|
37
|
+
catalogLocale: Locale.describe("The locale whose catalog answers `t()` — the words the kit actually has."),
|
|
38
|
+
formattingLocale: Locale.describe("The locale handed to `Intl` — what the reader asked for, region and all."),
|
|
39
|
+
direction: TextDirection.describe("The catalog locale's text direction, for `lang`/`dir` on the document.")
|
|
40
|
+
}).describe("Per-request resolved locale; the seam capabilities read instead of negotiating their own.");
|
|
41
|
+
/**
|
|
42
|
+
* `tag` as an `Intl.Locale`, or `null` when it is not one.
|
|
43
|
+
*
|
|
44
|
+
* **Every construction on header- or adopter-supplied input goes through this.** `new Intl.Locale()`
|
|
45
|
+
* throws `RangeError` on `*`, on `en_US`, on the empty string, and on any token still carrying
|
|
46
|
+
* `;q=0.9` — so a malformed `Accept-Language` is an uncaught 500 rather than a fallback, unless the
|
|
47
|
+
* throw is caught somewhere. It is caught here, once, and nowhere else.
|
|
48
|
+
*/
|
|
49
|
+
function parseLocale(tag) {
|
|
50
|
+
if (!LANGUAGE_TAG.test(tag) || tag.length > 64) return null;
|
|
51
|
+
try {
|
|
52
|
+
return new Intl.Locale(tag);
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The part of `tag` that may be handed to `Intl` as a **formatting** locale, or `null` when it is not a
|
|
59
|
+
* tag at all: language, script, region and variants, with every extension subtag stripped.
|
|
60
|
+
*
|
|
61
|
+
* **A `-u-` extension is not a locale, it is an instruction**, and the range a formatting locale is
|
|
62
|
+
* built from is caller-supplied. `?lang=en-u-nu-hanidec` truncates to `en`, matches a project that
|
|
63
|
+
* ships English, and — kept whole — reaches `Intl.NumberFormat` verbatim, where it renders `1,234` as
|
|
64
|
+
* `一,二三四`. `-u-ca-islamic` does the same to every date on the page. Nothing is disclosed and nothing
|
|
65
|
+
* persists, so it is spoofing rather than a breach; it is also a link somebody can send, and the whole
|
|
66
|
+
* point of resolving against `supportedLocales` is that a reader cannot pick a rendering the project
|
|
67
|
+
* did not offer.
|
|
68
|
+
*
|
|
69
|
+
* `baseName` is exactly the right cut, and is why this is a helper rather than a regex: `es-AR`,
|
|
70
|
+
* `zh-Hant-TW`, `es-419` and `de-DE-1996` all survive whole, because none of them is an extension.
|
|
71
|
+
*/
|
|
72
|
+
function formattingLocaleOf(tag) {
|
|
73
|
+
return parseLocale(tag)?.baseName ?? null;
|
|
74
|
+
}
|
|
75
|
+
/** Whether `tag` is a language tag `Intl` will accept — the predicate half of {@link parseLocale}. */
|
|
76
|
+
function isLocale(tag) {
|
|
77
|
+
return parseLocale(tag) !== null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The languages written right-to-left, for the runtimes that expose neither text-info accessor.
|
|
81
|
+
*
|
|
82
|
+
* A last resort, not the answer: `getTextInfo()` knows about scripts this list cannot name, and it is
|
|
83
|
+
* asked first. The five here are the ones a fallback has to get right.
|
|
84
|
+
*/
|
|
85
|
+
const RTL_LANGUAGES = /* @__PURE__ */ new Set([
|
|
86
|
+
"ar",
|
|
87
|
+
"he",
|
|
88
|
+
"fa",
|
|
89
|
+
"ur",
|
|
90
|
+
"ps"
|
|
91
|
+
]);
|
|
92
|
+
/**
|
|
93
|
+
* Which way `tag`'s script runs, feature-detected across both spellings and falling back to a small
|
|
94
|
+
* right-to-left language set when a runtime exposes neither.
|
|
95
|
+
*
|
|
96
|
+
* Total: an unparseable tag is `ltr`, because a document still needs a `dir` and guessing the other way
|
|
97
|
+
* mirrors a page nobody asked to mirror.
|
|
98
|
+
*/
|
|
99
|
+
function localeDirection(tag) {
|
|
100
|
+
const locale = parseLocale(tag);
|
|
101
|
+
if (!locale) return "ltr";
|
|
102
|
+
const withTextInfo = locale;
|
|
103
|
+
const info = typeof withTextInfo.getTextInfo === "function" ? withTextInfo.getTextInfo() : withTextInfo.textInfo;
|
|
104
|
+
if (info?.direction === "rtl") return "rtl";
|
|
105
|
+
if (info?.direction === "ltr") return "ltr";
|
|
106
|
+
return RTL_LANGUAGES.has(locale.language) ? "rtl" : "ltr";
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { Locale, LocaleContext, MAX_LOCALE_TAG_LENGTH, TextDirection, formattingLocaleOf, isLocale, localeDirection, parseLocale };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The locale tag this source declares in its head, or `null` if it declares none.
|
|
3
|
+
*
|
|
4
|
+
* `null` is the ordinary answer: almost every file in a tree declares nothing, and a file that does is saying
|
|
5
|
+
* something about its own values, not asking to be skipped. See this module's docblock for what a caller owes
|
|
6
|
+
* the answer — in particular that `en` comes back like any other tag and exempts nothing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function localeDeclared(source: string): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Every span on one line whose quoted content is a value rather than prose — as `[open, close)` offsets.
|
|
11
|
+
*
|
|
12
|
+
* `open` is the first character inside the quote and `close` is the closing quote, so a match at or after
|
|
13
|
+
* `open` and ending at or before `close` sits inside a value. Left to right, in whichever of the three quote
|
|
14
|
+
* characters opened it.
|
|
15
|
+
*
|
|
16
|
+
* By default a run holding whitespace is a sentence, not a value, and **a span that turns out to hold one is
|
|
17
|
+
* passed over rather than skipped**: the scan keeps reading inside it, which is how a backticked token inside
|
|
18
|
+
* a prose string literal still counts — this repository names wire values that way in a `.describe()`.
|
|
19
|
+
*
|
|
20
|
+
* `everyQuoted` widens that to include the sentences, for one case and one only: a line in a file that has
|
|
21
|
+
* declared itself written in another language, where the quoted runs are the translation. It is the mechanism
|
|
22
|
+
* the marker drives, and it is why the marker exempts a file's values without exempting the file.
|
|
23
|
+
*
|
|
24
|
+
* A run is one line's worth. A quote that never closes on its line opens no value, so the inner lines of a
|
|
25
|
+
* multi-line template literal are read as prose — deliberately, since that is where a long English argument
|
|
26
|
+
* would otherwise hide.
|
|
27
|
+
*/
|
|
28
|
+
export declare function valueSpans(line: string, everyQuoted?: boolean): [number, number][];
|
|
29
|
+
//# sourceMappingURL=localeMarker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localeMarker.d.ts","sourceRoot":"","sources":["../../src/i18n/localeMarker.ts"],"names":[],"mappings":"AAmEA;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM5D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAchF"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//#region src/i18n/localeMarker.ts
|
|
2
|
+
/**
|
|
3
|
+
* Reading the locale a source file declares in its head — the reader for the marker `docs/I18N.md` publishes.
|
|
4
|
+
*
|
|
5
|
+
* A prose census reads every committed file as text and refuses the spellings a project does not write. This
|
|
6
|
+
* repository runs one (`packages/cli/src/ci/americanEnglish.test.ts`: the words this project writes are
|
|
7
|
+
* American), and any project that ships translated copy eventually needs the same thing, because a catalog
|
|
8
|
+
* written in Spanish is prose that is *supposed* not to be English. A path list would answer that and is
|
|
9
|
+
* exactly what a census must not grow — a list is right on the day it is written, wrong afterwards, and the
|
|
10
|
+
* way it goes wrong is that the gate keeps passing. So the file says what it is instead, in one marker, in
|
|
11
|
+
* its head, where a reader meets it: the **locale** it is written in, and that the copy is an **unreviewed
|
|
12
|
+
* first pass**. Both facts are load-bearing and neither is checkable from the outside.
|
|
13
|
+
*
|
|
14
|
+
* ## The half that is easy to get backwards
|
|
15
|
+
*
|
|
16
|
+
* **The marker exempts the file's quoted values, not the file.** A catalog is not written in Spanish — its
|
|
17
|
+
* *values* are. Everything around them is a docblock, an import and an identifier: the project's own English
|
|
18
|
+
* prose, and exactly as subject to the rule as any other file's. So a declaring file widens what counts as a
|
|
19
|
+
* value — every quoted run, whole sentences included, rather than only the whitespace-free tokens exempt
|
|
20
|
+
* everywhere else — and every character outside those quotes is censused as it would be anywhere.
|
|
21
|
+
*
|
|
22
|
+
* Reading the marker as *skip this file* is the port that goes wrong, and it goes wrong quietly. It made this
|
|
23
|
+
* repository's own `es/errors.ts` a place where a twenty-seven-line English argument went unread, which is a
|
|
24
|
+
* gate that cannot fail on most of what it is looking at, and it costs an adopter the docblocks of every
|
|
25
|
+
* translated catalog they ever add.
|
|
26
|
+
*
|
|
27
|
+
* **`en` and `en-*` are a declaration and never an exemption**, which is why {@link localeDeclared} hands back
|
|
28
|
+
* the tag rather than a yes or a no. The words a project writes *in English* are the ones a census is about,
|
|
29
|
+
* and a marker cannot become a way to opt English prose out of it. The caller decides what a tag means;
|
|
30
|
+
* treating `en` as translated is the same mistake as treating `es` as skippable.
|
|
31
|
+
*
|
|
32
|
+
* ## Why it lives in `core/src/i18n`
|
|
33
|
+
*
|
|
34
|
+
* The folder is the import path, so it is named for the caller. The marker is a locale declaration — the same
|
|
35
|
+
* thing `./locale` and `./catalog` are about, one artifact earlier — and an adopter reaching for it is asking
|
|
36
|
+
* a question about translated copy, not about linting. It is deliberately not in the CLI: the CLI is a
|
|
37
|
+
* development tool an adopter's own test suite has no reason to depend on, and this is the kit's precedent for
|
|
38
|
+
* a pure textual scanner that a repository's tests want back (`../entitlement/gateScan`, which exports
|
|
39
|
+
* `withoutComments` for the same reason). No `node:` import, no filesystem: the caller brings the text,
|
|
40
|
+
* whether from a walked directory or from `import.meta.glob(…, "?raw")` inside a Workers-typed program.
|
|
41
|
+
*
|
|
42
|
+
* The census that owns the rest of the rule stays where it is. What ships here is the pair a port gets wrong.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* What a file written in a language other than English declares in its head.
|
|
46
|
+
*
|
|
47
|
+
* The exact spelling is published in `docs/I18N.md` — `LOCALE <tag>`, an em dash, and the unreviewed-first-pass
|
|
48
|
+
* sentence — and `packages/cli/src/ci/americanEnglish.test.ts` reads the line out of that document and holds it
|
|
49
|
+
* to this pattern, so an adopter copying the published marker cannot copy one that matches nothing. The tag is
|
|
50
|
+
* captured, never merely detected, because the caller's decision turns on which language it names.
|
|
51
|
+
*/
|
|
52
|
+
const LOCALE_DECLARED = /\bLOCALE ([a-z]{2,8}(?:-[A-Za-z0-9]{1,8})*) — an unreviewed first pass\./;
|
|
53
|
+
/**
|
|
54
|
+
* How far into a file the marker has to be, so a file mentioning it in prose is still read.
|
|
55
|
+
*
|
|
56
|
+
* A documented number rather than a tuning knob: `docs/I18N.md` publishes this window to adopters, and
|
|
57
|
+
* `packages/cli/src/ci/americanEnglish.test.ts` holds the count that document states to where this reader
|
|
58
|
+
* actually stops — so moving the number here fails the build until the document moves with it. This module's
|
|
59
|
+
* own test pins both sides of the boundary. It is an implementation detail of {@link localeDeclared} and is
|
|
60
|
+
* not exported — a caller hands over a whole source and gets an answer.
|
|
61
|
+
*/
|
|
62
|
+
const HEAD_LINES = 25;
|
|
63
|
+
/**
|
|
64
|
+
* The locale tag this source declares in its head, or `null` if it declares none.
|
|
65
|
+
*
|
|
66
|
+
* `null` is the ordinary answer: almost every file in a tree declares nothing, and a file that does is saying
|
|
67
|
+
* something about its own values, not asking to be skipped. See this module's docblock for what a caller owes
|
|
68
|
+
* the answer — in particular that `en` comes back like any other tag and exempts nothing.
|
|
69
|
+
*/
|
|
70
|
+
function localeDeclared(source) {
|
|
71
|
+
for (const line of source.split("\n").slice(0, HEAD_LINES)) {
|
|
72
|
+
const found = LOCALE_DECLARED.exec(line);
|
|
73
|
+
if (found !== null) return found[1];
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Every span on one line whose quoted content is a value rather than prose — as `[open, close)` offsets.
|
|
79
|
+
*
|
|
80
|
+
* `open` is the first character inside the quote and `close` is the closing quote, so a match at or after
|
|
81
|
+
* `open` and ending at or before `close` sits inside a value. Left to right, in whichever of the three quote
|
|
82
|
+
* characters opened it.
|
|
83
|
+
*
|
|
84
|
+
* By default a run holding whitespace is a sentence, not a value, and **a span that turns out to hold one is
|
|
85
|
+
* passed over rather than skipped**: the scan keeps reading inside it, which is how a backticked token inside
|
|
86
|
+
* a prose string literal still counts — this repository names wire values that way in a `.describe()`.
|
|
87
|
+
*
|
|
88
|
+
* `everyQuoted` widens that to include the sentences, for one case and one only: a line in a file that has
|
|
89
|
+
* declared itself written in another language, where the quoted runs are the translation. It is the mechanism
|
|
90
|
+
* the marker drives, and it is why the marker exempts a file's values without exempting the file.
|
|
91
|
+
*
|
|
92
|
+
* A run is one line's worth. A quote that never closes on its line opens no value, so the inner lines of a
|
|
93
|
+
* multi-line template literal are read as prose — deliberately, since that is where a long English argument
|
|
94
|
+
* would otherwise hide.
|
|
95
|
+
*/
|
|
96
|
+
function valueSpans(line, everyQuoted = false) {
|
|
97
|
+
const spans = [];
|
|
98
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
99
|
+
const quote = line[index];
|
|
100
|
+
if (quote !== "\"" && quote !== "'" && quote !== "`") continue;
|
|
101
|
+
const close = line.indexOf(quote, index + 1);
|
|
102
|
+
if (close === -1) continue;
|
|
103
|
+
const inner = line.slice(index + 1, close);
|
|
104
|
+
if (inner.length === 0) continue;
|
|
105
|
+
if (!everyQuoted && /\s/.test(inner)) continue;
|
|
106
|
+
spans.push([index + 1, close]);
|
|
107
|
+
index = close;
|
|
108
|
+
}
|
|
109
|
+
return spans;
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { localeDeclared, valueSpans };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A declared exception map: a language range the truncation walk would miss, and the supported locale
|
|
3
|
+
* it means. `{ nb: "no", tl: "fil" }` — the pairs no algorithm derives, because they are historical
|
|
4
|
+
* rather than structural.
|
|
5
|
+
*/
|
|
6
|
+
export type LocaleExceptions = Readonly<Record<string, string>>;
|
|
7
|
+
/** How a match was reached, so a caller can tell an answer from a default. */
|
|
8
|
+
export interface LocaleMatch {
|
|
9
|
+
/** The supported locale that answered, exactly as the caller spelled it in `supported`. */
|
|
10
|
+
readonly locale: string;
|
|
11
|
+
/** The range that matched it, lower-cased — `*` when a wildcard took the first supported locale. */
|
|
12
|
+
readonly range: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The first supported locale any of `desired` asks for, most-wanted first, or `null` when none does.
|
|
16
|
+
*
|
|
17
|
+
* `desired` is already in preference order — {@link parseAcceptLanguage} sorts by q-weight, and a
|
|
18
|
+
* browser resolver chain builds it from the reader's explicit choice downward. A `*` range takes the
|
|
19
|
+
* first supported locale, which is what "anything you have" means; it is checked in place rather than
|
|
20
|
+
* up front, so a reader who wrote `de, *` is answered in German when German ships.
|
|
21
|
+
*
|
|
22
|
+
* Total, and never throws: every `Intl` construction on the way through is guarded, so a header full
|
|
23
|
+
* of `en_US` and `;q=0.9` fragments falls through to `null` and the caller's default.
|
|
24
|
+
*/
|
|
25
|
+
export declare function matchLocale(desired: readonly string[], supported: readonly string[], exceptions?: LocaleExceptions): LocaleMatch | null;
|
|
26
|
+
//# sourceMappingURL=match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/i18n/match.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEhE,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oGAAoG;IACpG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AA+DD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,UAAU,GAAE,gBAAqB,GAChC,WAAW,GAAG,IAAI,CAcpB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { parseLocale } from "./locale.js";
|
|
2
|
+
//#region src/i18n/match.ts
|
|
3
|
+
/** Lower-cased index of the supported list, so matching is case-insensitive without lower-casing answers. */
|
|
4
|
+
function index(supported) {
|
|
5
|
+
const map = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const locale of supported) {
|
|
7
|
+
const key = locale.toLowerCase();
|
|
8
|
+
if (!map.has(key)) map.set(key, locale);
|
|
9
|
+
}
|
|
10
|
+
return map;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* RFC 4647 §3.4 lookup: `tag`, then `tag` with its right-most subtag removed, and so on.
|
|
14
|
+
*
|
|
15
|
+
* A trailing single-character subtag is removed with the one before it, as the RFC requires —
|
|
16
|
+
* `en-a-bbb` truncates to `en`, never to the meaningless `en-a`.
|
|
17
|
+
*/
|
|
18
|
+
function truncationWalk(tag, supported) {
|
|
19
|
+
let candidate = tag.toLowerCase();
|
|
20
|
+
while (candidate.length > 0) {
|
|
21
|
+
const hit = supported.get(candidate);
|
|
22
|
+
if (hit) return hit;
|
|
23
|
+
const cut = candidate.lastIndexOf("-");
|
|
24
|
+
if (cut < 0) return null;
|
|
25
|
+
candidate = candidate.slice(0, cut);
|
|
26
|
+
const tail = candidate.lastIndexOf("-");
|
|
27
|
+
if (tail >= 0 && candidate.length - tail === 2) candidate = candidate.slice(0, tail);
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The supported locale that best answers one range, or `null`.
|
|
33
|
+
*
|
|
34
|
+
* Three passes, in the order that makes each one earn its place: the declared exception map first,
|
|
35
|
+
* because it exists to override what follows; then the truncation walk on the range as written, which
|
|
36
|
+
* answers `es-AR` with `es`; then the walk again over the **maximized** form, which answers `zh-TW`
|
|
37
|
+
* with `zh-Hant` by way of `zh-Hant-TW`.
|
|
38
|
+
*
|
|
39
|
+
* **Maximizing is the third step, not the first.** `Intl.Locale` throws on input the first step handles
|
|
40
|
+
* fine, and maximizing a range before trying it as written turns `es` into `es-Latn-ES` and then walks
|
|
41
|
+
* back down to `es` — the same answer, three constructions later, and a `RangeError` away from a 500.
|
|
42
|
+
*/
|
|
43
|
+
function lookupRange(range, supported, exceptions) {
|
|
44
|
+
const key = range.toLowerCase();
|
|
45
|
+
const declared = Object.hasOwn(exceptions, key) ? exceptions[key] : void 0;
|
|
46
|
+
if (declared) {
|
|
47
|
+
const hit = supported.get(declared.toLowerCase());
|
|
48
|
+
if (hit) return hit;
|
|
49
|
+
}
|
|
50
|
+
const direct = truncationWalk(range, supported);
|
|
51
|
+
if (direct) return direct;
|
|
52
|
+
const maximized = parseLocale(range)?.maximize().toString();
|
|
53
|
+
return maximized ? truncationWalk(maximized, supported) : null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The first supported locale any of `desired` asks for, most-wanted first, or `null` when none does.
|
|
57
|
+
*
|
|
58
|
+
* `desired` is already in preference order — {@link parseAcceptLanguage} sorts by q-weight, and a
|
|
59
|
+
* browser resolver chain builds it from the reader's explicit choice downward. A `*` range takes the
|
|
60
|
+
* first supported locale, which is what "anything you have" means; it is checked in place rather than
|
|
61
|
+
* up front, so a reader who wrote `de, *` is answered in German when German ships.
|
|
62
|
+
*
|
|
63
|
+
* Total, and never throws: every `Intl` construction on the way through is guarded, so a header full
|
|
64
|
+
* of `en_US` and `;q=0.9` fragments falls through to `null` and the caller's default.
|
|
65
|
+
*/
|
|
66
|
+
function matchLocale(desired, supported, exceptions = {}) {
|
|
67
|
+
if (supported.length === 0) return null;
|
|
68
|
+
const lookup = index(supported);
|
|
69
|
+
for (const range of desired) {
|
|
70
|
+
const normalized = range.trim().toLowerCase();
|
|
71
|
+
if (normalized.length === 0) continue;
|
|
72
|
+
if (normalized === "*") {
|
|
73
|
+
const first = supported[0];
|
|
74
|
+
return first ? {
|
|
75
|
+
locale: first,
|
|
76
|
+
range: normalized
|
|
77
|
+
} : null;
|
|
78
|
+
}
|
|
79
|
+
const hit = lookupRange(normalized, lookup, exceptions);
|
|
80
|
+
if (hit) return {
|
|
81
|
+
locale: hit,
|
|
82
|
+
range: normalized
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { matchLocale };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Capability } from "../capability/capability";
|
|
2
|
+
import { type LocaleCatalogs, type MessageCatalog } from "./catalog";
|
|
3
|
+
/**
|
|
4
|
+
* Every composed capability's `messages`, merged into one set of catalogs keyed by locale.
|
|
5
|
+
*
|
|
6
|
+
* **A capability may only declare keys under its own domain**, and the merge refuses anything else.
|
|
7
|
+
* That is the same rule as `pithy_<capability>_<table>` and `auth/invalid_token`, enforced for the same
|
|
8
|
+
* reason: the domain segment is what makes two capabilities' contributions incapable of colliding, so
|
|
9
|
+
* merge order stops being a thing anyone has to reason about. It applies to the adopter's own `app`
|
|
10
|
+
* capability exactly as it applies to a kit package — `board/nav.settings` is theirs to declare, and
|
|
11
|
+
* `auth/sign_in.title` is not.
|
|
12
|
+
*
|
|
13
|
+
* Library-before-app, like the migration registry: capabilities are merged in composition order, so a
|
|
14
|
+
* later contribution wins a key an earlier one wrote. With the domain rule in force that can only
|
|
15
|
+
* happen between two capabilities sharing a name, which `createBackend` already refuses.
|
|
16
|
+
*/
|
|
17
|
+
export declare function composeMessages(capabilities: readonly Capability[]): LocaleCatalogs;
|
|
18
|
+
/**
|
|
19
|
+
* The catalog for `locale`, or the default locale's, or an empty one.
|
|
20
|
+
*
|
|
21
|
+
* The fallback that makes the seam optional: a Worker composing no i18n capability reads the baked
|
|
22
|
+
* English and behaves byte for byte as it did before any of this landed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function catalogFor(catalogs: LocaleCatalogs, locale: string): MessageCatalog;
|
|
25
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/i18n/registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAAiB,MAAM,WAAW,CAAC;AAWpF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,SAAS,UAAU,EAAE,GAAG,cAAc,CAqCnF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAEnF"}
|