@c15t/backend 1.2.1 → 1.2.2-canary-20250514183211

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.
@@ -1,53 +1,54 @@
1
-
2
- > @c15t/backend@1.2.1 build /home/runner/work/c15t/c15t/packages/backend
3
- > rslib build
4
-
5
- Rslib v0.6.2
6
-
7
- ready built in 1.18 s (esm)
8
- ready built in 1.18 s (cjs)
9
- start generating declaration files... (cjs)
10
- start generating declaration files... (esm)
11
- ready declaration files generated in 12.2 s (cjs)
12
- ready declaration files generated in 12.2 s (esm)
13
-
14
-  File (cjs) Size 
15
- dist/pkgs/types/index.cjs 0.60 kB
16
- dist/pkgs/data-model/fields/index.cjs 7.3 kB
17
- dist/pkgs/results/index.cjs 18.5 kB
18
- dist/contracts.cjs 30.6 kB
19
- dist/pkgs/db-adapters/adapters/memory-adapter/index.cjs 45.3 kB
20
- dist/router.cjs 48.3 kB
21
- dist/pkgs/db-adapters/adapters/prisma-adapter/index.cjs 48.4 kB
22
- dist/pkgs/db-adapters/adapters/drizzle-adapter/index.cjs 50.7 kB
23
- dist/pkgs/migrations/index.cjs 52.3 kB
24
- dist/pkgs/db-adapters/adapters/kysely-adapter/index.cjs 56.3 kB
25
- dist/pkgs/data-model/schema/index.cjs 64.0 kB
26
- dist/schema/index.cjs 64.5 kB
27
- dist/pkgs/data-model/index.cjs 66.0 kB
28
- dist/pkgs/db-adapters/index.cjs 90.8 kB
29
- dist/core.cjs 152.1 kB
30
-
31
- Total: 795.7 kB
32
-
33
-  -----
34
-
35
-  File (esm) Size 
36
- dist/pkgs/types/index.js 0.00 kB
37
- dist/pkgs/data-model/fields/index.js 5.6 kB
38
- dist/pkgs/results/index.js 16.4 kB
39
- dist/contracts.js 30.2 kB
40
- dist/pkgs/db-adapters/adapters/memory-adapter/index.js 44.3 kB
41
- dist/pkgs/db-adapters/adapters/prisma-adapter/index.js 47.5 kB
42
- dist/router.js 48.4 kB
43
- dist/pkgs/db-adapters/adapters/drizzle-adapter/index.js 50.0 kB
44
- dist/pkgs/migrations/index.js 50.7 kB
45
- dist/pkgs/db-adapters/adapters/kysely-adapter/index.js 55.3 kB
46
- dist/pkgs/data-model/schema/index.js 60.9 kB
47
- dist/schema/index.js 61.2 kB
48
- dist/pkgs/data-model/index.js 61.8 kB
49
- dist/pkgs/db-adapters/index.js 89.6 kB
50
- dist/core.js 153.1 kB
51
-
52
- Total: 774.9 kB
53
-
1
+
2
+ 
3
+ > @c15t/backend@1.2.1 build /Users/christopherburns/glados/k/c15t/packages/backend
4
+ > rslib build
5
+
6
+   Rslib v0.6.2
7
+ 
8
+ ready  built in 0.23 s (esm)
9
+ ready  built in 0.23 s (cjs)
10
+ start  generating declaration files... (esm)
11
+ start  generating declaration files... (cjs)
12
+ ready  declaration files generated in 3.49 s (cjs)
13
+ ready  declaration files generated in 3.51 s (esm)
14
+
15
+  File (esm) Size 
16
+ dist/pkgs/types/index.js 0.00 kB
17
+ dist/pkgs/data-model/fields/index.js 5.6 kB
18
+ dist/pkgs/results/index.js 16.4 kB
19
+ dist/contracts.js 30.2 kB
20
+ dist/pkgs/db-adapters/adapters/memory-adapter/index.js 44.3 kB
21
+ dist/pkgs/db-adapters/adapters/prisma-adapter/index.js 47.5 kB
22
+ dist/router.js 48.4 kB
23
+ dist/pkgs/db-adapters/adapters/drizzle-adapter/index.js 50.0 kB
24
+ dist/pkgs/migrations/index.js 50.7 kB
25
+ dist/pkgs/db-adapters/adapters/kysely-adapter/index.js 55.3 kB
26
+ dist/pkgs/data-model/schema/index.js 60.9 kB
27
+ dist/schema/index.js 61.2 kB
28
+ dist/pkgs/data-model/index.js 61.8 kB
29
+ dist/pkgs/db-adapters/index.js 89.6 kB
30
+ dist/core.js 153.1 kB
31
+
32
+ Total: 774.9 kB
33
+
34
+  -----
35
+
36
+  File (cjs) Size 
37
+ dist/pkgs/types/index.cjs 0.60 kB
38
+ dist/pkgs/data-model/fields/index.cjs 7.3 kB
39
+ dist/pkgs/results/index.cjs 18.5 kB
40
+ dist/contracts.cjs 30.6 kB
41
+ dist/pkgs/db-adapters/adapters/memory-adapter/index.cjs 45.3 kB
42
+ dist/router.cjs 48.3 kB
43
+ dist/pkgs/db-adapters/adapters/prisma-adapter/index.cjs 48.4 kB
44
+ dist/pkgs/db-adapters/adapters/drizzle-adapter/index.cjs 50.7 kB
45
+ dist/pkgs/migrations/index.cjs 52.3 kB
46
+ dist/pkgs/db-adapters/adapters/kysely-adapter/index.cjs 56.3 kB
47
+ dist/pkgs/data-model/schema/index.cjs 64.0 kB
48
+ dist/schema/index.cjs 64.5 kB
49
+ dist/pkgs/data-model/index.cjs 66.0 kB
50
+ dist/pkgs/db-adapters/index.cjs 90.8 kB
51
+ dist/core.cjs 152.1 kB
52
+
53
+ Total: 795.7 kB
54
+
@@ -0,0 +1,7 @@
1
+
2
+ 
3
+ > @c15t/backend@1.0.5 fmt /Users/christopherburns/glados/k/c15t/packages/backend
4
+ > pnpm biome format --write . && pnpm biome check --formatter-enabled=false --linter-enabled=false --organize-imports-enabled=true --write
5
+
6
+ Formatted 163 files in 31ms. No fixes applied.
7
+ Checked 163 files in 13ms. No fixes applied.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @c15t/backend
2
2
 
3
+ ## 1.2.2-canary-20250514183211
4
+
5
+ ### Patch Changes
6
+
7
+ - f64f000: feat: added @c15t/nextjs, @c15t/translations for better integration, server-side translations & fetching of consent banner
8
+ - Updated dependencies [f64f000]
9
+ - @c15t/translations@1.2.2-canary-20250514183211
10
+
3
11
  ## 1.2.1
4
12
 
5
13
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c15t/backend",
3
- "version": "1.2.1",
3
+ "version": "1.2.2-canary-20250514183211",
4
4
  "license": "GPL-3.0-only",
5
5
  "type": "module",
6
6
  "exports": {
@@ -99,7 +99,8 @@
99
99
  "kysely": "^0.27.6",
100
100
  "neverthrow": "^8.2.0",
101
101
  "superjson": "^2.2.2",
102
- "zod": "^3.24.2"
102
+ "zod": "^3.24.2",
103
+ "@c15t/translations": "1.2.2-canary-20250514183211"
103
104
  },
104
105
  "devDependencies": {
105
106
  "@electric-sql/pglite": "0.2.17",
@@ -1,3 +1,4 @@
1
+ import { baseTranslations } from '@c15t/translations';
1
2
  import { describe, expect, it } from 'vitest';
2
3
 
3
4
  // import { createContractTests } from '~/testing/contract-testing';
@@ -34,6 +35,10 @@ describe('Show Consent Banner Contract Custom Tests', () => {
34
35
  countryCode: 'DE',
35
36
  regionCode: null,
36
37
  },
38
+ translations: {
39
+ translations: baseTranslations.en,
40
+ language: 'en',
41
+ },
37
42
  };
38
43
 
39
44
  const result = validateOutput(validOutput);
@@ -75,6 +80,10 @@ describe('Show Consent Banner Contract Custom Tests', () => {
75
80
  countryCode: 'US',
76
81
  regionCode: null,
77
82
  },
83
+ translations: {
84
+ translations: baseTranslations.en,
85
+ language: 'en',
86
+ },
78
87
  };
79
88
 
80
89
  const result = validateOutput(output);
@@ -93,6 +102,10 @@ describe('Show Consent Banner Contract Custom Tests', () => {
93
102
  countryCode: 'US',
94
103
  regionCode: null,
95
104
  },
105
+ translations: {
106
+ translations: baseTranslations.en,
107
+ language: 'en',
108
+ },
96
109
  };
97
110
 
98
111
  const result = validateOutput(output);
@@ -112,6 +125,10 @@ describe('Show Consent Banner Contract Custom Tests', () => {
112
125
  countryCode: null,
113
126
  regionCode: null,
114
127
  },
128
+ translations: {
129
+ translations: baseTranslations.en,
130
+ language: 'en',
131
+ },
115
132
  };
116
133
 
117
134
  const result = validateOutput(output);
@@ -129,6 +146,10 @@ describe('Show Consent Banner Contract Custom Tests', () => {
129
146
  countryCode: 'DE',
130
147
  regionCode: 'BY',
131
148
  },
149
+ translations: {
150
+ translations: baseTranslations.en,
151
+ language: 'en',
152
+ },
132
153
  };
133
154
 
134
155
  const result = validateOutput(output);
@@ -146,6 +167,10 @@ describe('Show Consent Banner Contract Custom Tests', () => {
146
167
  countryCode: 123, // Invalid type
147
168
  regionCode: null,
148
169
  },
170
+ translations: {
171
+ translations: baseTranslations.en,
172
+ language: 'en',
173
+ },
149
174
  };
150
175
 
151
176
  // Need to use type assertion to bypass TypeScript
@@ -166,6 +191,10 @@ describe('Show Consent Banner Contract Custom Tests', () => {
166
191
  countryCode: 'DE',
167
192
  regionCode: null,
168
193
  },
194
+ translations: {
195
+ translations: baseTranslations.en,
196
+ language: 'en',
197
+ },
169
198
  };
170
199
 
171
200
  const falseOutput = {
@@ -178,6 +207,10 @@ describe('Show Consent Banner Contract Custom Tests', () => {
178
207
  countryCode: 'US',
179
208
  regionCode: null,
180
209
  },
210
+ translations: {
211
+ translations: baseTranslations.en,
212
+ language: 'en',
213
+ },
181
214
  };
182
215
 
183
216
  expect(validateOutput(trueOutput)?.success).toBe(true);
@@ -195,6 +228,10 @@ describe('Show Consent Banner Contract Custom Tests', () => {
195
228
  countryCode: 'DE',
196
229
  regionCode: null,
197
230
  },
231
+ translations: {
232
+ translations: baseTranslations.en,
233
+ language: 'en',
234
+ },
198
235
  };
199
236
 
200
237
  // Need to use type assertion to bypass TypeScript
@@ -1,8 +1,30 @@
1
1
  import { oc } from '@orpc/contract';
2
2
  import { z } from 'zod';
3
-
4
3
  import { JurisdictionInfoSchema } from '../shared/jurisdiction.schema';
5
4
 
5
+ const TitleDescriptionSchema = z.object({
6
+ title: z.string(),
7
+ description: z.string(),
8
+ });
9
+
10
+ const TranslationsSchema = z.object({
11
+ common: z.object({
12
+ acceptAll: z.string(),
13
+ rejectAll: z.string(),
14
+ customize: z.string(),
15
+ save: z.string(),
16
+ }),
17
+ cookieBanner: TitleDescriptionSchema,
18
+ consentManagerDialog: TitleDescriptionSchema,
19
+ consentTypes: z.object({
20
+ experience: TitleDescriptionSchema,
21
+ functionality: TitleDescriptionSchema,
22
+ marketing: TitleDescriptionSchema,
23
+ measurement: TitleDescriptionSchema,
24
+ necessary: TitleDescriptionSchema,
25
+ }),
26
+ });
27
+
6
28
  export const showConsentBannerContract = oc
7
29
  .route({
8
30
  method: 'GET',
@@ -41,5 +63,9 @@ Use this endpoint to implement geo-targeted consent banners and ensure complianc
41
63
  countryCode: z.string().nullable(),
42
64
  regionCode: z.string().nullable(),
43
65
  }),
66
+ translations: z.object({
67
+ language: z.string(),
68
+ translations: TranslationsSchema,
69
+ }),
44
70
  })
45
71
  );
@@ -1,3 +1,4 @@
1
+ import { baseTranslations } from '@c15t/translations';
1
2
  import { describe, expect, it, vi } from 'vitest';
2
3
  import { JurisdictionMessages } from '~/contracts/shared/jurisdiction.schema';
3
4
  import { showConsentBanner } from './show-banner.handler';
@@ -125,6 +126,47 @@ describe('Show Consent Banner Handler', () => {
125
126
  });
126
127
  });
127
128
 
129
+ describe('Translation handling', () => {
130
+ it('returns default translations when no language is detected', async () => {
131
+ //@ts-expect-error
132
+ const result = await showConsentBanner(
133
+ createMockContext({ 'cf-ipcountry': 'DE' })
134
+ );
135
+
136
+ expect(result.translations).toEqual({
137
+ translations: baseTranslations.en,
138
+ language: 'en',
139
+ });
140
+ });
141
+
142
+ it('returns translations for detected language', async () => {
143
+ //@ts-expect-error
144
+ const result = await showConsentBanner(
145
+ createMockContext({ 'cf-ipcountry': 'DE', 'accept-language': 'de-DE' })
146
+ );
147
+
148
+ expect(result.translations).toEqual({
149
+ translations: baseTranslations.de,
150
+ language: 'de',
151
+ });
152
+ });
153
+
154
+ it('returns default translations when language is not supported', async () => {
155
+ //@ts-expect-error
156
+ const result = await showConsentBanner(
157
+ createMockContext({
158
+ 'cf-ipcountry': 'DE',
159
+ 'accept-language': 'foobar',
160
+ })
161
+ );
162
+
163
+ expect(result.translations).toEqual({
164
+ translations: baseTranslations.en,
165
+ language: 'en',
166
+ });
167
+ });
168
+ });
169
+
128
170
  describe('Response format', () => {
129
171
  it('returns properly structured response', async () => {
130
172
  //@ts-expect-error
@@ -142,6 +184,10 @@ describe('Show Consent Banner Handler', () => {
142
184
  countryCode: 'DE',
143
185
  regionCode: null,
144
186
  },
187
+ translations: {
188
+ translations: baseTranslations.en,
189
+ language: 'en',
190
+ },
145
191
  });
146
192
  });
147
193
  });
@@ -1,3 +1,4 @@
1
+ import { baseTranslations } from '@c15t/translations';
1
2
  import { ORPCError } from '@orpc/server';
2
3
  import { os } from '~/contracts';
3
4
  import {
@@ -6,6 +7,34 @@ import {
6
7
  } from '~/contracts/shared/jurisdiction.schema';
7
8
  import type { C15TContext } from '~/types';
8
9
 
10
+ type SupportedLanguage = keyof typeof baseTranslations;
11
+
12
+ /**
13
+ * Extracts the preferred language from Accept-Language header
14
+ * Falls back to 'en' if no supported language is found
15
+ */
16
+ function getPreferredLanguage(
17
+ acceptLanguage: string | null
18
+ ): SupportedLanguage {
19
+ if (!acceptLanguage) {
20
+ return 'en';
21
+ }
22
+
23
+ // Get the primary language code
24
+ const primaryLang = acceptLanguage
25
+ .split(',')[0]
26
+ ?.split(';')[0]
27
+ ?.split('-')[0]
28
+ ?.toLowerCase();
29
+
30
+ // Check if it's a supported language
31
+ if (primaryLang && primaryLang in baseTranslations) {
32
+ return primaryLang as SupportedLanguage;
33
+ }
34
+
35
+ return 'en';
36
+ }
37
+
9
38
  /**
10
39
  * Handler for the show consent banner endpoint
11
40
  * Determines if a user should see a consent banner based on their location
@@ -45,13 +74,15 @@ export const showConsentBanner = os.consent.showBanner.handler(
45
74
  normalizeHeader(headers.get('x-vercel-ip-country-region')) ??
46
75
  normalizeHeader(headers.get('x-region-code'));
47
76
 
48
- // If no location headers found, throw error
77
+ // Get preferred language from Accept-Language header
78
+ const acceptLanguage = normalizeHeader(headers.get('accept-language'));
79
+ const preferredLanguage = getPreferredLanguage(acceptLanguage);
49
80
 
50
81
  // Determine jurisdiction based on country
51
82
  const { showConsentBanner, jurisdictionCode, message } =
52
83
  checkJurisdiction(countryCode);
53
84
 
54
- // Return properly structured response
85
+ // Return properly structured response with translations
55
86
  return {
56
87
  showConsentBanner,
57
88
  jurisdiction: {
@@ -59,6 +90,10 @@ export const showConsentBanner = os.consent.showBanner.handler(
59
90
  message,
60
91
  },
61
92
  location: { countryCode, regionCode },
93
+ translations: {
94
+ translations: baseTranslations[preferredLanguage],
95
+ language: preferredLanguage,
96
+ },
62
97
  };
63
98
  }
64
99
  );