@c15t/backend 2.0.0-rc.0 → 2.0.0-rc.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 +3 -3
- package/dist/302.js +473 -0
- package/dist/583.js +540 -0
- package/dist/915.js +1771 -0
- package/dist/cache.cjs +5 -5
- package/dist/cache.js +4 -415
- package/dist/core.cjs +1356 -120
- package/dist/core.js +163 -1981
- package/dist/db/adapters/drizzle.cjs +1 -1
- package/dist/db/adapters/drizzle.js +1 -2
- package/dist/db/adapters/kysely.cjs +1 -1
- package/dist/db/adapters/kysely.js +1 -2
- package/dist/db/adapters/mongo.cjs +1 -1
- package/dist/db/adapters/mongo.js +1 -2
- package/dist/db/adapters/prisma.cjs +1 -1
- package/dist/db/adapters/prisma.js +1 -2
- package/dist/db/adapters/typeorm.cjs +1 -1
- package/dist/db/adapters/typeorm.js +1 -2
- package/dist/db/adapters.cjs +1 -1
- package/dist/db/migrator.cjs +1 -1
- package/dist/db/schema.cjs +43 -3
- package/dist/db/schema.js +35 -4
- package/dist/define-config.cjs +1 -1
- package/dist/edge.cjs +1106 -0
- package/dist/edge.js +190 -0
- package/dist/router.cjs +885 -123
- package/dist/router.js +1 -1507
- package/dist/{types.cjs → types/index.cjs} +1 -1
- package/{dist → dist-types}/cache/adapters/cloudflare-kv.d.ts +0 -1
- package/{dist → dist-types}/cache/adapters/index.d.ts +0 -1
- package/{dist → dist-types}/cache/adapters/memory.d.ts +0 -1
- package/{dist → dist-types}/cache/adapters/upstash-redis.d.ts +0 -1
- package/{dist → dist-types}/cache/gvl-resolver.d.ts +0 -1
- package/{dist → dist-types}/cache/index.d.ts +0 -1
- package/{dist → dist-types}/cache/keys.d.ts +0 -1
- package/{dist → dist-types}/cache/types.d.ts +0 -1
- package/{dist → dist-types}/core.d.ts +8 -1
- package/{dist → dist-types}/db/migrator/index.d.ts +0 -1
- package/dist-types/db/registry/consent-policy.d.ts +78 -0
- package/{dist → dist-types}/db/registry/consent-purpose.d.ts +0 -1
- package/{dist → dist-types}/db/registry/domain.d.ts +0 -1
- package/dist-types/db/registry/index.d.ts +118 -0
- package/dist-types/db/registry/runtime-policy-decision.d.ts +60 -0
- package/{dist → dist-types}/db/registry/subject.d.ts +0 -2
- package/{dist → dist-types}/db/registry/types.d.ts +1 -1
- package/{dist → dist-types}/db/registry/utils/generate-id.d.ts +0 -1
- package/{dist → dist-types}/db/registry/utils.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/audit-log.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/consent-policy.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/consent-purpose.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/consent-record.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/consent.d.ts +1 -2
- package/{dist → dist-types}/db/schema/1.0.0/domain.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/index.d.ts +0 -32
- package/{dist → dist-types}/db/schema/1.0.0/subject.d.ts +0 -2
- package/{dist → dist-types}/db/schema/2.0.0/audit-log.d.ts +1 -2
- package/{dist → dist-types}/db/schema/2.0.0/consent-policy.d.ts +3 -3
- package/{dist → dist-types}/db/schema/2.0.0/consent-purpose.d.ts +1 -2
- package/{dist → dist-types}/db/schema/2.0.0/consent.d.ts +7 -2
- package/{dist → dist-types}/db/schema/2.0.0/domain.d.ts +1 -2
- package/{dist → dist-types}/db/schema/2.0.0/index.d.ts +455 -28
- package/dist-types/db/schema/2.0.0/runtime-policy-decision.d.ts +23 -0
- package/{dist → dist-types}/db/schema/2.0.0/subject.d.ts +1 -3
- package/{dist → dist-types}/db/schema/index.d.ts +908 -86
- package/{dist → dist-types}/db/tenant-scope.d.ts +0 -1
- package/dist-types/define-config.d.ts +17 -0
- package/dist-types/edge/index.d.ts +5 -0
- package/dist-types/edge/init-handler.d.ts +40 -0
- package/dist-types/edge/resolve-consent.d.ts +80 -0
- package/dist-types/edge/types.d.ts +13 -0
- package/{dist → dist-types}/handlers/consent/check.handler.d.ts +0 -1
- package/{src/handlers/consent/index.ts → dist-types/handlers/consent/index.d.ts} +0 -1
- package/{dist → dist-types}/handlers/init/geo.d.ts +2 -3
- package/{dist → dist-types}/handlers/init/index.d.ts +2 -3
- package/dist-types/handlers/init/policy.d.ts +26 -0
- package/dist-types/handlers/init/resolve-init.d.ts +44 -0
- package/dist-types/handlers/init/translations.d.ts +48 -0
- package/dist-types/handlers/legal-document/current.handler.d.ts +11 -0
- package/dist-types/handlers/legal-document/snapshot.d.ts +39 -0
- package/dist-types/handlers/policy/snapshot.d.ts +99 -0
- package/{src/handlers/status/index.ts → dist-types/handlers/status/index.d.ts} +0 -1
- package/{dist → dist-types}/handlers/status/status.handler.d.ts +0 -1
- package/{dist → dist-types}/handlers/subject/get.handler.d.ts +3 -2
- package/{src/handlers/subject/index.ts → dist-types/handlers/subject/index.d.ts} +0 -1
- package/{dist → dist-types}/handlers/subject/list.handler.d.ts +3 -2
- package/{dist → dist-types}/handlers/subject/patch.handler.d.ts +0 -2
- package/{dist → dist-types}/handlers/subject/post.handler.d.ts +12 -1
- package/{dist → dist-types}/handlers/utils/consent-enrichment.d.ts +3 -1
- package/{dist → dist-types}/init.d.ts +4 -7
- package/{dist → dist-types}/middleware/auth/index.d.ts +0 -1
- package/{dist → dist-types}/middleware/auth/validate-api-key.d.ts +0 -1
- package/{dist → dist-types}/middleware/cors/cors.d.ts +0 -1
- package/{src/middleware/cors/index.ts → dist-types/middleware/cors/index.d.ts} +0 -1
- package/{dist → dist-types}/middleware/cors/is-origin-trusted.d.ts +0 -1
- package/{dist → dist-types}/middleware/cors/process-cors.d.ts +0 -1
- package/{dist → dist-types}/middleware/openapi/config.d.ts +0 -1
- package/{dist → dist-types}/middleware/openapi/handlers.d.ts +0 -1
- package/{src/middleware/openapi/index.ts → dist-types/middleware/openapi/index.d.ts} +0 -1
- package/{dist → dist-types}/middleware/process-ip/index.d.ts +0 -1
- package/dist-types/policies/builder.d.ts +127 -0
- package/dist-types/policies/defaults.d.ts +2 -0
- package/dist-types/policies/matchers.d.ts +3 -0
- package/{dist → dist-types}/router.d.ts +0 -1
- package/{dist → dist-types}/routes/consent.d.ts +0 -1
- package/{dist → dist-types}/routes/index.d.ts +1 -1
- package/{dist → dist-types}/routes/init.d.ts +0 -1
- package/dist-types/routes/legal-document.d.ts +7 -0
- package/{dist → dist-types}/routes/status.d.ts +0 -1
- package/{dist → dist-types}/routes/subject.d.ts +0 -1
- package/{dist → dist-types}/types/api.d.ts +0 -1
- package/dist-types/types/index.d.ts +464 -0
- package/dist-types/utils/background.d.ts +6 -0
- package/{dist → dist-types}/utils/create-telemetry-options.d.ts +1 -2
- package/{dist → dist-types}/utils/env.d.ts +0 -1
- package/{dist → dist-types}/utils/extract-error-message.d.ts +0 -1
- package/{dist → dist-types}/utils/instrumentation.d.ts +2 -3
- package/{dist → dist-types}/utils/logger.d.ts +0 -1
- package/{dist → dist-types}/utils/metrics.d.ts +0 -1
- package/dist-types/version.d.ts +1 -0
- package/docs/README.md +49 -0
- package/docs/api/configuration.md +208 -0
- package/docs/api/endpoints.md +211 -0
- package/docs/guides/caching.md +85 -0
- package/docs/guides/database-setup.md +128 -0
- package/docs/guides/edge-deployment.md +251 -0
- package/docs/guides/framework-integration.md +142 -0
- package/docs/guides/iab-tcf.md +89 -0
- package/docs/guides/observability.md +96 -0
- package/docs/guides/policy-packs.md +396 -0
- package/docs/quickstart.md +129 -0
- package/package.json +53 -39
- package/.turbo/turbo-build.log +0 -49
- package/CHANGELOG.md +0 -89
- package/dist/cache/adapters/cloudflare-kv.d.ts.map +0 -1
- package/dist/cache/adapters/index.d.ts.map +0 -1
- package/dist/cache/adapters/memory.d.ts.map +0 -1
- package/dist/cache/adapters/upstash-redis.d.ts.map +0 -1
- package/dist/cache/gvl-resolver.d.ts.map +0 -1
- package/dist/cache/index.d.ts.map +0 -1
- package/dist/cache/keys.d.ts.map +0 -1
- package/dist/cache/types.d.ts.map +0 -1
- package/dist/core.d.ts.map +0 -1
- package/dist/db/adapters/drizzle.d.ts +0 -2
- package/dist/db/adapters/drizzle.d.ts.map +0 -1
- package/dist/db/adapters/index.d.ts +0 -2
- package/dist/db/adapters/index.d.ts.map +0 -1
- package/dist/db/adapters/kysely.d.ts +0 -2
- package/dist/db/adapters/kysely.d.ts.map +0 -1
- package/dist/db/adapters/mongo.d.ts +0 -2
- package/dist/db/adapters/mongo.d.ts.map +0 -1
- package/dist/db/adapters/prisma.d.ts +0 -2
- package/dist/db/adapters/prisma.d.ts.map +0 -1
- package/dist/db/adapters/typeorm.d.ts +0 -2
- package/dist/db/adapters/typeorm.d.ts.map +0 -1
- package/dist/db/migrator/index.d.ts.map +0 -1
- package/dist/db/registry/consent-policy.d.ts +0 -23
- package/dist/db/registry/consent-policy.d.ts.map +0 -1
- package/dist/db/registry/consent-purpose.d.ts.map +0 -1
- package/dist/db/registry/domain.d.ts.map +0 -1
- package/dist/db/registry/index.d.ts +0 -57
- package/dist/db/registry/index.d.ts.map +0 -1
- package/dist/db/registry/subject.d.ts.map +0 -1
- package/dist/db/registry/types.d.ts.map +0 -1
- package/dist/db/registry/utils/generate-id.d.ts.map +0 -1
- package/dist/db/registry/utils.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/audit-log.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/consent-policy.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/consent-purpose.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/consent-record.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/consent.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/domain.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/index.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/subject.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/audit-log.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/consent-policy.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/consent-purpose.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/consent.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/domain.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/index.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/subject.d.ts.map +0 -1
- package/dist/db/schema/index.d.ts.map +0 -1
- package/dist/db/tenant-scope.d.ts.map +0 -1
- package/dist/define-config.d.ts +0 -5
- package/dist/define-config.d.ts.map +0 -1
- package/dist/handlers/consent/check.handler.d.ts.map +0 -1
- package/dist/handlers/consent/index.d.ts +0 -12
- package/dist/handlers/consent/index.d.ts.map +0 -1
- package/dist/handlers/init/geo.d.ts.map +0 -1
- package/dist/handlers/init/index.d.ts.map +0 -1
- package/dist/handlers/init/translations.d.ts +0 -28
- package/dist/handlers/init/translations.d.ts.map +0 -1
- package/dist/handlers/status/index.d.ts +0 -7
- package/dist/handlers/status/index.d.ts.map +0 -1
- package/dist/handlers/status/status.handler.d.ts.map +0 -1
- package/dist/handlers/subject/get.handler.d.ts.map +0 -1
- package/dist/handlers/subject/index.d.ts +0 -10
- package/dist/handlers/subject/index.d.ts.map +0 -1
- package/dist/handlers/subject/list.handler.d.ts.map +0 -1
- package/dist/handlers/subject/patch.handler.d.ts.map +0 -1
- package/dist/handlers/subject/post.handler.d.ts.map +0 -1
- package/dist/handlers/utils/consent-enrichment.d.ts.map +0 -1
- package/dist/init.d.ts.map +0 -1
- package/dist/middleware/auth/index.d.ts.map +0 -1
- package/dist/middleware/auth/validate-api-key.d.ts.map +0 -1
- package/dist/middleware/cors/cors.d.ts.map +0 -1
- package/dist/middleware/cors/index.d.ts +0 -30
- package/dist/middleware/cors/index.d.ts.map +0 -1
- package/dist/middleware/cors/is-origin-trusted.d.ts.map +0 -1
- package/dist/middleware/cors/process-cors.d.ts.map +0 -1
- package/dist/middleware/openapi/config.d.ts.map +0 -1
- package/dist/middleware/openapi/handlers.d.ts.map +0 -1
- package/dist/middleware/openapi/index.d.ts +0 -12
- package/dist/middleware/openapi/index.d.ts.map +0 -1
- package/dist/middleware/process-ip/index.d.ts.map +0 -1
- package/dist/router.d.ts.map +0 -1
- package/dist/routes/consent.d.ts.map +0 -1
- package/dist/routes/index.d.ts.map +0 -1
- package/dist/routes/init.d.ts.map +0 -1
- package/dist/routes/status.d.ts.map +0 -1
- package/dist/routes/subject.d.ts.map +0 -1
- package/dist/types/api.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -255
- package/dist/types/index.d.ts.map +0 -1
- package/dist/utils/create-telemetry-options.d.ts.map +0 -1
- package/dist/utils/env.d.ts.map +0 -1
- package/dist/utils/extract-error-message.d.ts.map +0 -1
- package/dist/utils/index.d.ts +0 -4
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/instrumentation.d.ts.map +0 -1
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/metrics.d.ts.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +0 -1
- package/knip.json +0 -31
- package/rslib.config.ts +0 -93
- package/src/cache/adapters/cloudflare-kv.ts +0 -71
- package/src/cache/adapters/index.ts +0 -22
- package/src/cache/adapters/memory.ts +0 -111
- package/src/cache/adapters/upstash-redis.ts +0 -113
- package/src/cache/gvl-resolver.ts +0 -289
- package/src/cache/index.ts +0 -34
- package/src/cache/keys.ts +0 -68
- package/src/cache/types.ts +0 -66
- package/src/core.ts +0 -368
- package/src/db/migrator/index.ts +0 -80
- package/src/db/registry/consent-policy.test.ts +0 -451
- package/src/db/registry/consent-policy.ts +0 -82
- package/src/db/registry/consent-purpose.test.ts +0 -428
- package/src/db/registry/consent-purpose.ts +0 -61
- package/src/db/registry/domain.test.ts +0 -445
- package/src/db/registry/domain.ts +0 -91
- package/src/db/registry/index.ts +0 -14
- package/src/db/registry/subject.test.ts +0 -388
- package/src/db/registry/subject.ts +0 -129
- package/src/db/registry/types.ts +0 -10
- package/src/db/registry/utils/generate-id.test.ts +0 -216
- package/src/db/registry/utils/generate-id.ts +0 -133
- package/src/db/registry/utils.ts +0 -133
- package/src/db/schema/1.0.0/audit-log.ts +0 -15
- package/src/db/schema/1.0.0/consent-policy.ts +0 -14
- package/src/db/schema/1.0.0/consent-purpose.ts +0 -14
- package/src/db/schema/1.0.0/consent-record.ts +0 -10
- package/src/db/schema/1.0.0/consent.ts +0 -20
- package/src/db/schema/1.0.0/domain.ts +0 -12
- package/src/db/schema/1.0.0/index.ts +0 -48
- package/src/db/schema/1.0.0/subject.ts +0 -12
- package/src/db/schema/2.0.0/audit-log.ts +0 -18
- package/src/db/schema/2.0.0/consent-policy.ts +0 -28
- package/src/db/schema/2.0.0/consent-purpose.ts +0 -12
- package/src/db/schema/2.0.0/consent.ts +0 -26
- package/src/db/schema/2.0.0/domain.ts +0 -12
- package/src/db/schema/2.0.0/index.ts +0 -47
- package/src/db/schema/2.0.0/subject.ts +0 -14
- package/src/db/schema/index.ts +0 -15
- package/src/db/tenant-scope.test.ts +0 -750
- package/src/db/tenant-scope.ts +0 -103
- package/src/define-config.ts +0 -5
- package/src/handlers/consent/check.handler.ts +0 -126
- package/src/handlers/init/geo.test.ts +0 -317
- package/src/handlers/init/geo.ts +0 -195
- package/src/handlers/init/index.test.ts +0 -205
- package/src/handlers/init/index.ts +0 -114
- package/src/handlers/init/translations.test.ts +0 -121
- package/src/handlers/init/translations.ts +0 -72
- package/src/handlers/status/status.handler.test.ts +0 -155
- package/src/handlers/status/status.handler.ts +0 -51
- package/src/handlers/subject/get.handler.ts +0 -93
- package/src/handlers/subject/list.handler.ts +0 -93
- package/src/handlers/subject/patch.handler.ts +0 -122
- package/src/handlers/subject/post.handler.test.ts +0 -294
- package/src/handlers/subject/post.handler.ts +0 -254
- package/src/handlers/utils/consent-enrichment.test.ts +0 -380
- package/src/handlers/utils/consent-enrichment.ts +0 -218
- package/src/init.test.ts +0 -126
- package/src/init.ts +0 -87
- package/src/middleware/auth/index.ts +0 -11
- package/src/middleware/auth/validate-api-key.test.ts +0 -86
- package/src/middleware/auth/validate-api-key.ts +0 -107
- package/src/middleware/cors/cors.test.ts +0 -135
- package/src/middleware/cors/cors.ts +0 -186
- package/src/middleware/cors/is-origin-trusted.test.ts +0 -164
- package/src/middleware/cors/is-origin-trusted.ts +0 -130
- package/src/middleware/cors/process-cors.ts +0 -91
- package/src/middleware/openapi/config.ts +0 -29
- package/src/middleware/openapi/handlers.ts +0 -34
- package/src/middleware/process-ip/index.test.ts +0 -195
- package/src/middleware/process-ip/index.ts +0 -199
- package/src/router.ts +0 -15
- package/src/routes/consent.ts +0 -52
- package/src/routes/index.ts +0 -10
- package/src/routes/init.ts +0 -102
- package/src/routes/status.ts +0 -46
- package/src/routes/subject.ts +0 -152
- package/src/types/api.ts +0 -48
- package/src/types/index.ts +0 -288
- package/src/utils/create-telemetry-options.test.ts +0 -302
- package/src/utils/create-telemetry-options.ts +0 -229
- package/src/utils/env.ts +0 -84
- package/src/utils/extract-error-message.ts +0 -21
- package/src/utils/instrumentation.test.ts +0 -185
- package/src/utils/instrumentation.ts +0 -196
- package/src/utils/logger.ts +0 -41
- package/src/utils/metrics.test.ts +0 -323
- package/src/utils/metrics.ts +0 -402
- package/src/utils/telemetry-pii.test.ts +0 -325
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -11
- package/vitest.config.ts +0 -28
- /package/dist/{types.js → types/index.js} +0 -0
- /package/{src/db/adapters/drizzle.ts → dist-types/db/adapters/drizzle.d.ts} +0 -0
- /package/{src/db/adapters/index.ts → dist-types/db/adapters/index.d.ts} +0 -0
- /package/{src/db/adapters/kysely.ts → dist-types/db/adapters/kysely.d.ts} +0 -0
- /package/{src/db/adapters/mongo.ts → dist-types/db/adapters/mongo.d.ts} +0 -0
- /package/{src/db/adapters/prisma.ts → dist-types/db/adapters/prisma.d.ts} +0 -0
- /package/{src/db/adapters/typeorm.ts → dist-types/db/adapters/typeorm.d.ts} +0 -0
- /package/{src/utils/index.ts → dist-types/utils/index.d.ts} +0 -0
package/src/handlers/init/geo.ts
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import type { C15TOptions } from '~/types';
|
|
2
|
-
import type { JurisdictionCode } from '~/types/api';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Normalizes a header value to a string or null.
|
|
6
|
-
*/
|
|
7
|
-
function normalizeHeader(
|
|
8
|
-
value: string | string[] | null | undefined
|
|
9
|
-
): string | null {
|
|
10
|
-
if (!value) {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
return Array.isArray(value) ? (value[0] ?? null) : value;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Gets geo-related headers from the request.
|
|
18
|
-
*/
|
|
19
|
-
function getGeoHeaders(headers: Headers) {
|
|
20
|
-
const countryCode =
|
|
21
|
-
normalizeHeader(headers.get('x-c15t-country')) ??
|
|
22
|
-
normalizeHeader(headers.get('cf-ipcountry')) ??
|
|
23
|
-
normalizeHeader(headers.get('x-vercel-ip-country')) ??
|
|
24
|
-
normalizeHeader(headers.get('x-amz-cf-ipcountry')) ??
|
|
25
|
-
normalizeHeader(headers.get('x-country-code'));
|
|
26
|
-
|
|
27
|
-
const regionCode =
|
|
28
|
-
normalizeHeader(headers.get('x-c15t-region')) ??
|
|
29
|
-
normalizeHeader(headers.get('x-vercel-ip-country-region')) ??
|
|
30
|
-
normalizeHeader(headers.get('x-region-code'));
|
|
31
|
-
|
|
32
|
-
return { countryCode, regionCode };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Determines the applicable jurisdiction based on country and region codes.
|
|
37
|
-
*
|
|
38
|
-
* @remarks
|
|
39
|
-
* - EU/EEA/UK map to GDPR-style regimes.
|
|
40
|
-
* - Specific countries map to their local laws (CH, BR, CA, AU, JP, KR).
|
|
41
|
-
* - CCPA is applied for certain US regions (e.g. California).
|
|
42
|
-
*/
|
|
43
|
-
export function checkJurisdiction(
|
|
44
|
-
countryCode: string | null,
|
|
45
|
-
regionCode?: string | null
|
|
46
|
-
): JurisdictionCode {
|
|
47
|
-
// Country/region sets for different jurisdictions
|
|
48
|
-
const jurisdictions = {
|
|
49
|
-
EU: new Set([
|
|
50
|
-
'AT',
|
|
51
|
-
'BE',
|
|
52
|
-
'BG',
|
|
53
|
-
'HR',
|
|
54
|
-
'CY',
|
|
55
|
-
'CZ',
|
|
56
|
-
'DK',
|
|
57
|
-
'EE',
|
|
58
|
-
'FI',
|
|
59
|
-
'FR',
|
|
60
|
-
'DE',
|
|
61
|
-
'GR',
|
|
62
|
-
'HU',
|
|
63
|
-
'IE',
|
|
64
|
-
'IT',
|
|
65
|
-
'LV',
|
|
66
|
-
'LT',
|
|
67
|
-
'LU',
|
|
68
|
-
'MT',
|
|
69
|
-
'NL',
|
|
70
|
-
'PL',
|
|
71
|
-
'PT',
|
|
72
|
-
'RO',
|
|
73
|
-
'SK',
|
|
74
|
-
'SI',
|
|
75
|
-
'ES',
|
|
76
|
-
'SE',
|
|
77
|
-
]),
|
|
78
|
-
EEA: new Set(['IS', 'NO', 'LI']),
|
|
79
|
-
UK: new Set(['GB']),
|
|
80
|
-
CH: new Set(['CH']),
|
|
81
|
-
BR: new Set(['BR']),
|
|
82
|
-
CA: new Set(['CA']),
|
|
83
|
-
AU: new Set(['AU']),
|
|
84
|
-
JP: new Set(['JP']),
|
|
85
|
-
KR: new Set(['KR']),
|
|
86
|
-
US_CCPA_REGIONS: new Set([
|
|
87
|
-
// California (CCPA/CPRA)
|
|
88
|
-
'CA',
|
|
89
|
-
]),
|
|
90
|
-
CA_QC_REGIONS: new Set([
|
|
91
|
-
// Quebec (Law 25)
|
|
92
|
-
'QC',
|
|
93
|
-
]),
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// Default to no jurisdiction
|
|
97
|
-
let jurisdiction: JurisdictionCode = 'NONE';
|
|
98
|
-
|
|
99
|
-
// Check country/region codes against jurisdiction sets
|
|
100
|
-
if (countryCode) {
|
|
101
|
-
// Normalize for case-insensitive comparison
|
|
102
|
-
const normalizedCountryCode = countryCode.toUpperCase();
|
|
103
|
-
// Normalize region code: handle dash-separated formats like "CA-QC" or "US-CA"
|
|
104
|
-
// by extracting the last segment as the subdivision code
|
|
105
|
-
const normalizedRegionCode =
|
|
106
|
-
regionCode && typeof regionCode === 'string'
|
|
107
|
-
? (regionCode.includes('-')
|
|
108
|
-
? regionCode.split('-').pop()!
|
|
109
|
-
: regionCode
|
|
110
|
-
).toUpperCase()
|
|
111
|
-
: null;
|
|
112
|
-
|
|
113
|
-
// CCPA-style rules: currently applied for certain US regions only
|
|
114
|
-
if (
|
|
115
|
-
normalizedCountryCode === 'US' &&
|
|
116
|
-
normalizedRegionCode &&
|
|
117
|
-
jurisdictions.US_CCPA_REGIONS.has(normalizedRegionCode)
|
|
118
|
-
) {
|
|
119
|
-
return 'CCPA';
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Quebec (Law 25): opt-in consent required
|
|
123
|
-
if (
|
|
124
|
-
normalizedCountryCode === 'CA' &&
|
|
125
|
-
normalizedRegionCode &&
|
|
126
|
-
jurisdictions.CA_QC_REGIONS.has(normalizedRegionCode)
|
|
127
|
-
) {
|
|
128
|
-
return 'QC_LAW25';
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const jurisdictionMap = [
|
|
132
|
-
{
|
|
133
|
-
sets: [jurisdictions.UK],
|
|
134
|
-
code: 'UK_GDPR' as const,
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
sets: [jurisdictions.EU, jurisdictions.EEA],
|
|
138
|
-
code: 'GDPR' as const,
|
|
139
|
-
},
|
|
140
|
-
{ sets: [jurisdictions.CH], code: 'CH' as const },
|
|
141
|
-
{ sets: [jurisdictions.BR], code: 'BR' as const },
|
|
142
|
-
{ sets: [jurisdictions.CA], code: 'PIPEDA' as const },
|
|
143
|
-
{ sets: [jurisdictions.AU], code: 'AU' as const },
|
|
144
|
-
{ sets: [jurisdictions.JP], code: 'APPI' as const },
|
|
145
|
-
{ sets: [jurisdictions.KR], code: 'PIPA' as const },
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
// Find matching jurisdiction
|
|
149
|
-
for (const { sets, code } of jurisdictionMap) {
|
|
150
|
-
if (sets.some((set) => set.has(normalizedCountryCode))) {
|
|
151
|
-
jurisdiction = code;
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return jurisdiction;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Gets the location from the request headers.
|
|
162
|
-
*
|
|
163
|
-
* @param request - The incoming request
|
|
164
|
-
* @param options - The C15T options
|
|
165
|
-
* @returns The location object with countryCode and regionCode
|
|
166
|
-
*/
|
|
167
|
-
export async function getLocation(
|
|
168
|
-
request: Request,
|
|
169
|
-
options: C15TOptions
|
|
170
|
-
): Promise<{ countryCode: string | null; regionCode: string | null }> {
|
|
171
|
-
if (options.advanced?.disableGeoLocation) {
|
|
172
|
-
return { countryCode: null, regionCode: null };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const { countryCode, regionCode } = getGeoHeaders(request.headers);
|
|
176
|
-
return { countryCode, regionCode };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Gets the jurisdiction based on location and options.
|
|
181
|
-
*
|
|
182
|
-
* @param location - The location object
|
|
183
|
-
* @param options - The C15T options
|
|
184
|
-
* @returns The jurisdiction code
|
|
185
|
-
*/
|
|
186
|
-
export function getJurisdiction(
|
|
187
|
-
location: { countryCode: string | null; regionCode: string | null },
|
|
188
|
-
options: C15TOptions
|
|
189
|
-
): JurisdictionCode {
|
|
190
|
-
if (options.advanced?.disableGeoLocation) {
|
|
191
|
-
return 'GDPR';
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return checkJurisdiction(location.countryCode, location.regionCode);
|
|
195
|
-
}
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { baseTranslations } from '@c15t/translations';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import type { C15TOptions } from '~/types';
|
|
4
|
-
import { checkJurisdiction, getJurisdiction, getLocation } from './geo';
|
|
5
|
-
import {
|
|
6
|
-
buildResponse,
|
|
7
|
-
getHeaders,
|
|
8
|
-
getTranslationsData,
|
|
9
|
-
parseAcceptLanguage,
|
|
10
|
-
} from './index';
|
|
11
|
-
|
|
12
|
-
describe('Init Handler Utilities', () => {
|
|
13
|
-
describe('getHeaders', () => {
|
|
14
|
-
it('extracts country code from cf-ipcountry header', () => {
|
|
15
|
-
const headers = new Headers({ 'cf-ipcountry': 'DE' });
|
|
16
|
-
const result = getHeaders(headers);
|
|
17
|
-
expect(result.countryCode).toBe('DE');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('falls back to alternative country code headers', () => {
|
|
21
|
-
const headers = new Headers({ 'x-vercel-ip-country': 'FR' });
|
|
22
|
-
const result = getHeaders(headers);
|
|
23
|
-
expect(result.countryCode).toBe('FR');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('prioritizes cf-ipcountry over other headers', () => {
|
|
27
|
-
const headers = new Headers({
|
|
28
|
-
'cf-ipcountry': 'DE',
|
|
29
|
-
'x-vercel-ip-country': 'FR',
|
|
30
|
-
});
|
|
31
|
-
const result = getHeaders(headers);
|
|
32
|
-
expect(result.countryCode).toBe('DE');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('extracts region code from headers', () => {
|
|
36
|
-
const headers = new Headers({
|
|
37
|
-
'cf-ipcountry': 'US',
|
|
38
|
-
'x-vercel-ip-country-region': 'CA',
|
|
39
|
-
});
|
|
40
|
-
const result = getHeaders(headers);
|
|
41
|
-
expect(result.countryCode).toBe('US');
|
|
42
|
-
expect(result.regionCode).toBe('CA');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('handles missing headers gracefully', () => {
|
|
46
|
-
const headers = new Headers({});
|
|
47
|
-
const result = getHeaders(headers);
|
|
48
|
-
expect(result.countryCode).toBeNull();
|
|
49
|
-
expect(result.regionCode).toBeNull();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('handles undefined headers', () => {
|
|
53
|
-
const result = getHeaders(undefined);
|
|
54
|
-
expect(result.countryCode).toBeNull();
|
|
55
|
-
expect(result.regionCode).toBeNull();
|
|
56
|
-
expect(result.acceptLanguage).toBeNull();
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('checkJurisdiction', () => {
|
|
61
|
-
it('returns GDPR for EU countries', () => {
|
|
62
|
-
expect(checkJurisdiction('DE', null)).toBe('GDPR');
|
|
63
|
-
expect(checkJurisdiction('FR', null)).toBe('GDPR');
|
|
64
|
-
expect(checkJurisdiction('IT', null)).toBe('GDPR');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('returns UK_GDPR for UK', () => {
|
|
68
|
-
expect(checkJurisdiction('GB', null)).toBe('UK_GDPR');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('returns CCPA for California', () => {
|
|
72
|
-
expect(checkJurisdiction('US', 'CA')).toBe('CCPA');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('returns NONE for non-regulated regions', () => {
|
|
76
|
-
expect(checkJurisdiction('US', null)).toBe('NONE');
|
|
77
|
-
expect(checkJurisdiction('US', 'TX')).toBe('NONE');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('returns PIPEDA for Canada', () => {
|
|
81
|
-
expect(checkJurisdiction('CA', null)).toBe('PIPEDA');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('returns CH for Switzerland', () => {
|
|
85
|
-
expect(checkJurisdiction('CH', null)).toBe('CH');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('handles null country code', () => {
|
|
89
|
-
expect(checkJurisdiction(null, null)).toBe('NONE');
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('parseAcceptLanguage', () => {
|
|
94
|
-
it('returns en for null input', () => {
|
|
95
|
-
expect(parseAcceptLanguage(null)).toBe('en');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('parses simple language code', () => {
|
|
99
|
-
expect(parseAcceptLanguage('de')).toBe('de');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('parses language with region', () => {
|
|
103
|
-
expect(parseAcceptLanguage('de-DE')).toBe('de');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('parses Accept-Language with quality factors', () => {
|
|
107
|
-
expect(parseAcceptLanguage('de-DE,de;q=0.9,en;q=0.8')).toBe('de');
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('getTranslationsData', () => {
|
|
112
|
-
it('returns en translations for null Accept-Language', () => {
|
|
113
|
-
const result = getTranslationsData(null);
|
|
114
|
-
expect(result.language).toBe('en');
|
|
115
|
-
expect(result.translations.cookieBanner.title).toBe(
|
|
116
|
-
baseTranslations.en.cookieBanner.title
|
|
117
|
-
);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('returns de translations for de-DE', () => {
|
|
121
|
-
const result = getTranslationsData('de-DE,de;q=0.9,en;q=0.8');
|
|
122
|
-
expect(result.language).toBe('de');
|
|
123
|
-
expect(result.translations.cookieBanner.title).toBe(
|
|
124
|
-
baseTranslations.de.cookieBanner.title
|
|
125
|
-
);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('merges custom translations', () => {
|
|
129
|
-
const customTranslations = {
|
|
130
|
-
en: { cookieBanner: { title: 'Custom Title' } },
|
|
131
|
-
};
|
|
132
|
-
const result = getTranslationsData('en-US', customTranslations);
|
|
133
|
-
expect(result.translations.cookieBanner.title).toBe('Custom Title');
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
describe('buildResponse', () => {
|
|
138
|
-
it('returns properly structured response', () => {
|
|
139
|
-
const result = buildResponse({
|
|
140
|
-
jurisdiction: 'GDPR',
|
|
141
|
-
location: { countryCode: 'DE', regionCode: null },
|
|
142
|
-
acceptLanguage: 'en',
|
|
143
|
-
customTranslations: undefined,
|
|
144
|
-
branding: 'c15t',
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
expect(result).toHaveProperty('jurisdiction');
|
|
148
|
-
expect(result).toHaveProperty('location');
|
|
149
|
-
expect(result).toHaveProperty('translations');
|
|
150
|
-
expect(result).toHaveProperty('branding');
|
|
151
|
-
expect(result.jurisdiction).toBe('GDPR');
|
|
152
|
-
expect(result.branding).toBe('c15t');
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
describe('getLocation', () => {
|
|
157
|
-
it('returns null location when geo is disabled', async () => {
|
|
158
|
-
const request = new Request('http://localhost', {
|
|
159
|
-
headers: { 'cf-ipcountry': 'DE' },
|
|
160
|
-
});
|
|
161
|
-
const options: C15TOptions = {
|
|
162
|
-
trustedOrigins: [],
|
|
163
|
-
adapter: {} as C15TOptions['adapter'],
|
|
164
|
-
advanced: { disableGeoLocation: true },
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const result = await getLocation(request, options);
|
|
168
|
-
expect(result.countryCode).toBeNull();
|
|
169
|
-
expect(result.regionCode).toBeNull();
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
describe('getJurisdiction', () => {
|
|
174
|
-
it('returns GDPR when geo is disabled', () => {
|
|
175
|
-
const options: C15TOptions = {
|
|
176
|
-
trustedOrigins: [],
|
|
177
|
-
adapter: {} as C15TOptions['adapter'],
|
|
178
|
-
advanced: { disableGeoLocation: true },
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const result = getJurisdiction(
|
|
182
|
-
{ countryCode: 'US', regionCode: null },
|
|
183
|
-
options
|
|
184
|
-
);
|
|
185
|
-
expect(result).toBe('GDPR');
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('returns appropriate jurisdiction based on location', () => {
|
|
189
|
-
const options: C15TOptions = {
|
|
190
|
-
trustedOrigins: [],
|
|
191
|
-
adapter: {} as C15TOptions['adapter'],
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
expect(
|
|
195
|
-
getJurisdiction({ countryCode: 'DE', regionCode: null }, options)
|
|
196
|
-
).toBe('GDPR');
|
|
197
|
-
expect(
|
|
198
|
-
getJurisdiction({ countryCode: 'US', regionCode: 'CA' }, options)
|
|
199
|
-
).toBe('CCPA');
|
|
200
|
-
expect(
|
|
201
|
-
getJurisdiction({ countryCode: 'GB', regionCode: null }, options)
|
|
202
|
-
).toBe('UK_GDPR');
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import type { GlobalVendorList, NonIABVendor } from '@c15t/schema/types';
|
|
2
|
-
import type { Translations } from '@c15t/translations';
|
|
3
|
-
import type { Branding } from '~/types';
|
|
4
|
-
import type { JurisdictionCode } from '~/types/api';
|
|
5
|
-
import { getTranslationsData } from './translations';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Gets the headers from the context.
|
|
9
|
-
*
|
|
10
|
-
* @param headers - The headers to get the headers from.
|
|
11
|
-
* @returns The headers or null if the headers are not present.
|
|
12
|
-
*/
|
|
13
|
-
export function getHeaders(headers: Headers | undefined) {
|
|
14
|
-
if (!headers) {
|
|
15
|
-
return {
|
|
16
|
-
countryCode: null,
|
|
17
|
-
regionCode: null,
|
|
18
|
-
acceptLanguage: null,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Add this conversion to ensure headers are always string or null
|
|
23
|
-
const normalizeHeader = (
|
|
24
|
-
value: string | string[] | null | undefined
|
|
25
|
-
): string | null => {
|
|
26
|
-
if (!value) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return Array.isArray(value) ? (value[0] ?? null) : value;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const countryCode =
|
|
34
|
-
normalizeHeader(headers.get('x-c15t-country')) ??
|
|
35
|
-
normalizeHeader(headers.get('cf-ipcountry')) ??
|
|
36
|
-
normalizeHeader(headers.get('x-vercel-ip-country')) ??
|
|
37
|
-
normalizeHeader(headers.get('x-amz-cf-ipcountry')) ??
|
|
38
|
-
normalizeHeader(headers.get('x-country-code'));
|
|
39
|
-
|
|
40
|
-
const regionCode =
|
|
41
|
-
normalizeHeader(headers.get('x-c15t-region')) ??
|
|
42
|
-
normalizeHeader(headers.get('x-vercel-ip-country-region')) ??
|
|
43
|
-
normalizeHeader(headers.get('x-region-code'));
|
|
44
|
-
|
|
45
|
-
// Get preferred language from Accept-Language header
|
|
46
|
-
const acceptLanguage = normalizeHeader(headers.get('accept-language'));
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
countryCode,
|
|
50
|
-
regionCode,
|
|
51
|
-
acceptLanguage,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Parse Accept-Language header to get the primary language code.
|
|
57
|
-
*
|
|
58
|
-
* @param acceptLanguage - The Accept-Language header value
|
|
59
|
-
* @returns The primary language code (e.g., "en", "de"), defaults to "en"
|
|
60
|
-
*/
|
|
61
|
-
export function parseAcceptLanguage(acceptLanguage: string | null): string {
|
|
62
|
-
if (!acceptLanguage) {
|
|
63
|
-
return 'en';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Parse "en-US,en;q=0.9,de;q=0.8" format
|
|
67
|
-
// Split by comma, take the first part, then extract language code
|
|
68
|
-
const firstLanguage = acceptLanguage.split(',')[0];
|
|
69
|
-
if (!firstLanguage) {
|
|
70
|
-
return 'en';
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Remove quality factor if present (e.g., "en;q=0.9" -> "en")
|
|
74
|
-
const languageWithRegion = firstLanguage.split(';')[0]?.trim();
|
|
75
|
-
if (!languageWithRegion) {
|
|
76
|
-
return 'en';
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Extract language code without region (e.g., "en-US" -> "en")
|
|
80
|
-
const languageCode = languageWithRegion.split('-')[0]?.toLowerCase();
|
|
81
|
-
|
|
82
|
-
return languageCode ?? 'en';
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function buildResponse({
|
|
86
|
-
jurisdiction,
|
|
87
|
-
location,
|
|
88
|
-
acceptLanguage,
|
|
89
|
-
customTranslations,
|
|
90
|
-
branding = 'c15t',
|
|
91
|
-
gvl,
|
|
92
|
-
customVendors,
|
|
93
|
-
}: {
|
|
94
|
-
jurisdiction: JurisdictionCode;
|
|
95
|
-
location: { countryCode: string | null; regionCode: string | null };
|
|
96
|
-
acceptLanguage: string | null;
|
|
97
|
-
customTranslations: Record<string, Partial<Translations>> | undefined;
|
|
98
|
-
branding?: Branding;
|
|
99
|
-
gvl?: GlobalVendorList;
|
|
100
|
-
customVendors?: NonIABVendor[];
|
|
101
|
-
}) {
|
|
102
|
-
return {
|
|
103
|
-
jurisdiction,
|
|
104
|
-
location,
|
|
105
|
-
translations: getTranslationsData(acceptLanguage, customTranslations),
|
|
106
|
-
branding: branding,
|
|
107
|
-
gvl: gvl ?? null,
|
|
108
|
-
customVendors: customVendors ?? undefined,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export { checkJurisdiction, getJurisdiction, getLocation } from './geo';
|
|
113
|
-
// Re-export translations functions
|
|
114
|
-
export { getTranslations, getTranslationsData } from './translations';
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { baseTranslations } from '@c15t/translations';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { getTranslationsData } from './translations';
|
|
4
|
-
|
|
5
|
-
describe('showBanner > getTranslationsData', () => {
|
|
6
|
-
it("should return 'en' translations when Accept-Language is null", () => {
|
|
7
|
-
const { translations, language } = getTranslationsData(null);
|
|
8
|
-
expect(language).toBe('en');
|
|
9
|
-
expect(translations.cookieBanner.title).toBe(
|
|
10
|
-
baseTranslations.en.cookieBanner.title
|
|
11
|
-
);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("should return 'en' translations for unsupported language", () => {
|
|
15
|
-
const { translations, language } = getTranslationsData('xx-XX,en;q=0.9');
|
|
16
|
-
expect(language).toBe('en');
|
|
17
|
-
expect(translations.cookieBanner.title).toBe(
|
|
18
|
-
baseTranslations.en.cookieBanner.title
|
|
19
|
-
);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("should return 'de' translations for 'de-DE'", () => {
|
|
23
|
-
const { translations, language } = getTranslationsData(
|
|
24
|
-
'de-DE,de;q=0.9,en;q=0.8'
|
|
25
|
-
);
|
|
26
|
-
expect(language).toBe('de');
|
|
27
|
-
expect(translations.cookieBanner.title).toBe(
|
|
28
|
-
baseTranslations.de.cookieBanner.title
|
|
29
|
-
);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should merge custom translations for the preferred language', () => {
|
|
33
|
-
const customTranslations = {
|
|
34
|
-
en: {
|
|
35
|
-
cookieBanner: {
|
|
36
|
-
title: 'My Custom Cookie Title',
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
const { translations, language } = getTranslationsData(
|
|
41
|
-
'en-US,en;q=0.9',
|
|
42
|
-
customTranslations
|
|
43
|
-
);
|
|
44
|
-
expect(language).toBe('en');
|
|
45
|
-
expect(translations.cookieBanner.title).toBe('My Custom Cookie Title');
|
|
46
|
-
// Check if other properties from base translations are still there
|
|
47
|
-
expect(translations.cookieBanner.description).toBeDefined();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should not merge custom translations for a different language', () => {
|
|
51
|
-
const customTranslations = {
|
|
52
|
-
de: {
|
|
53
|
-
cookieBanner: {
|
|
54
|
-
title: 'Meine benutzerdefinierte Cookie-Überschrift',
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
const { translations, language } = getTranslationsData(
|
|
59
|
-
'en-US,en;q=0.9',
|
|
60
|
-
customTranslations
|
|
61
|
-
);
|
|
62
|
-
expect(language).toBe('en');
|
|
63
|
-
expect(translations.cookieBanner.title).toBe(
|
|
64
|
-
baseTranslations.en.cookieBanner.title
|
|
65
|
-
);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should handle partially provided custom translations', () => {
|
|
69
|
-
const customTranslations = {
|
|
70
|
-
en: {
|
|
71
|
-
cookieBanner: {
|
|
72
|
-
// Title is NOT provided, description is.
|
|
73
|
-
description: 'My custom description.',
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
const { translations, language } = getTranslationsData(
|
|
78
|
-
'en-US,en;q=0.9',
|
|
79
|
-
customTranslations
|
|
80
|
-
);
|
|
81
|
-
expect(language).toBe('en');
|
|
82
|
-
// Title should come from base
|
|
83
|
-
expect(translations.cookieBanner.title).toBe(
|
|
84
|
-
baseTranslations.en.cookieBanner.title
|
|
85
|
-
);
|
|
86
|
-
// Description should be from custom
|
|
87
|
-
expect(translations.cookieBanner.description).toBe(
|
|
88
|
-
'My custom description.'
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should return base translations if custom translations are empty for the language', () => {
|
|
93
|
-
const customTranslations = {
|
|
94
|
-
de: {},
|
|
95
|
-
};
|
|
96
|
-
const { translations, language } = getTranslationsData(
|
|
97
|
-
'de-DE,de;q=0.9',
|
|
98
|
-
customTranslations
|
|
99
|
-
);
|
|
100
|
-
expect(language).toBe('de');
|
|
101
|
-
expect(translations.cookieBanner.title).toBe(
|
|
102
|
-
baseTranslations.de.cookieBanner.title
|
|
103
|
-
);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should return custom translations for unsupported base language', () => {
|
|
107
|
-
const { translations, language } = getTranslationsData('xx-XX,en;q=0.9', {
|
|
108
|
-
xx: {
|
|
109
|
-
cookieBanner: {
|
|
110
|
-
title: 'XX Title',
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
expect(language).toBe('xx');
|
|
116
|
-
expect(translations.cookieBanner.title).toBe('XX Title');
|
|
117
|
-
expect(translations.cookieBanner.description).toBe(
|
|
118
|
-
baseTranslations.en.cookieBanner.description
|
|
119
|
-
);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
baseTranslations,
|
|
3
|
-
type CompleteTranslations,
|
|
4
|
-
deepMergeTranslations,
|
|
5
|
-
selectLanguage,
|
|
6
|
-
type Translations,
|
|
7
|
-
} from '@c15t/translations';
|
|
8
|
-
|
|
9
|
-
type SupportedBaseLanguage = keyof typeof baseTranslations;
|
|
10
|
-
|
|
11
|
-
function isSupportedBaseLanguage(lang: string): lang is SupportedBaseLanguage {
|
|
12
|
-
return lang in baseTranslations;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Gets the translations data for a given language, merging custom translations if provided.
|
|
17
|
-
*
|
|
18
|
-
* @param acceptLanguage - The `Accept-Language` header from the request.
|
|
19
|
-
* @param customTranslations - An object containing custom translations to merge with the base translations.
|
|
20
|
-
* @returns An object containing the final translations and the determined language.
|
|
21
|
-
*/
|
|
22
|
-
export function getTranslationsData(
|
|
23
|
-
acceptLanguage: string | null,
|
|
24
|
-
customTranslations?: Record<string, Partial<Translations>>
|
|
25
|
-
) {
|
|
26
|
-
const supportedDefaultLanguages = Object.keys(baseTranslations);
|
|
27
|
-
const supportedCustomLanguages = Object.keys(customTranslations || {});
|
|
28
|
-
|
|
29
|
-
const supportedLanguages = [
|
|
30
|
-
...supportedDefaultLanguages,
|
|
31
|
-
...supportedCustomLanguages,
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
const preferredLanguage = selectLanguage(supportedLanguages, {
|
|
35
|
-
header: acceptLanguage,
|
|
36
|
-
fallback: 'en',
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const base = isSupportedBaseLanguage(preferredLanguage)
|
|
40
|
-
? baseTranslations[preferredLanguage]
|
|
41
|
-
: baseTranslations.en;
|
|
42
|
-
|
|
43
|
-
const custom = supportedCustomLanguages.includes(preferredLanguage)
|
|
44
|
-
? customTranslations?.[preferredLanguage]
|
|
45
|
-
: {};
|
|
46
|
-
|
|
47
|
-
const translations = custom ? deepMergeTranslations(base, custom) : base;
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
translations: translations as CompleteTranslations,
|
|
51
|
-
language: preferredLanguage,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Gets the translations for a given language from options.
|
|
57
|
-
*
|
|
58
|
-
* @param acceptLanguage - The `Accept-Language` header from the request.
|
|
59
|
-
* @param options - The C15T options containing custom translations.
|
|
60
|
-
* @returns An object containing the final translations and the determined language.
|
|
61
|
-
*/
|
|
62
|
-
export async function getTranslations(
|
|
63
|
-
acceptLanguage: string,
|
|
64
|
-
options: {
|
|
65
|
-
advanced?: { customTranslations?: Record<string, Partial<Translations>> };
|
|
66
|
-
}
|
|
67
|
-
) {
|
|
68
|
-
return getTranslationsData(
|
|
69
|
-
acceptLanguage,
|
|
70
|
-
options.advanced?.customTranslations
|
|
71
|
-
);
|
|
72
|
-
}
|