@djangocfg/ext-base 1.0.16 → 1.0.17

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/api.js CHANGED
@@ -1,2 +1,2 @@
1
- export { createExtensionAPI, getSharedAuthStorage, initializeExtensionAPI } from './chunk-555TSGMD.js';
1
+ export { createExtensionAPI, getSharedAuthStorage, initializeExtensionAPI } from './chunk-Q4JJNC2D.js';
2
2
  import './chunk-3RG5ZIWI.js';
@@ -1,4 +1,4 @@
1
- import { package_default } from './chunk-555TSGMD.js';
1
+ import { package_default } from './chunk-Q4JJNC2D.js';
2
2
  import { createConsola } from 'consola';
3
3
 
4
4
  // src/types/context.ts
@@ -3,7 +3,7 @@ import { __require } from './chunk-3RG5ZIWI.js';
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@djangocfg/ext-base",
6
- version: "1.0.16",
6
+ version: "1.0.17",
7
7
  description: "Base utilities and common code for DjangoCFG extensions",
8
8
  keywords: [
9
9
  "django",
@@ -52,6 +52,11 @@ var package_default = {
52
52
  types: "./dist/api.d.ts",
53
53
  import: "./dist/api.js",
54
54
  require: "./dist/api.cjs"
55
+ },
56
+ "./i18n": {
57
+ types: "./dist/i18n.d.ts",
58
+ import: "./dist/i18n.js",
59
+ require: "./dist/i18n.cjs"
55
60
  }
56
61
  },
57
62
  files: [
package/dist/hooks.cjs CHANGED
@@ -33,7 +33,7 @@ var EXTENSION_CATEGORIES = [
33
33
  // package.json
34
34
  var package_default = {
35
35
  name: "@djangocfg/ext-base",
36
- version: "1.0.16",
36
+ version: "1.0.17",
37
37
  description: "Base utilities and common code for DjangoCFG extensions",
38
38
  keywords: [
39
39
  "django",
package/dist/hooks.js CHANGED
@@ -1,7 +1,7 @@
1
- import { createExtensionLogger } from './chunk-RKB5BOPI.js';
2
- export { EXTENSION_CATEGORIES, createExtensionConfig, createExtensionError, createExtensionLogger, extensionConfig, formatErrorMessage, handleExtensionError, isExtensionError } from './chunk-RKB5BOPI.js';
3
- import { isDevelopment } from './chunk-555TSGMD.js';
4
- export { createExtensionAPI, getApiUrl, getSharedAuthStorage, initializeExtensionAPI, isDevelopment, isProduction, isStaticBuild } from './chunk-555TSGMD.js';
1
+ import { createExtensionLogger } from './chunk-AU2RS5WG.js';
2
+ export { EXTENSION_CATEGORIES, createExtensionConfig, createExtensionError, createExtensionLogger, extensionConfig, formatErrorMessage, handleExtensionError, isExtensionError } from './chunk-AU2RS5WG.js';
3
+ import { isDevelopment } from './chunk-Q4JJNC2D.js';
4
+ export { createExtensionAPI, getApiUrl, getSharedAuthStorage, initializeExtensionAPI, isDevelopment, isProduction, isStaticBuild } from './chunk-Q4JJNC2D.js';
5
5
  import './chunk-3RG5ZIWI.js';
6
6
  import React, { createContext, useEffect, useContext } from 'react';
7
7
  import { jsx, Fragment } from 'react/jsx-runtime';
package/dist/i18n.cjs ADDED
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ // src/i18n/utils.ts
4
+ function createExtensionI18n(config) {
5
+ const { namespace, defaultLocale, locales } = config;
6
+ return {
7
+ namespace,
8
+ defaultLocale,
9
+ locales: Object.keys(locales),
10
+ getTranslations(locale) {
11
+ return locales[locale] ?? locales[defaultLocale];
12
+ },
13
+ getAllTranslations() {
14
+ const result = {};
15
+ for (const [locale, translations] of Object.entries(locales)) {
16
+ result[locale] = {
17
+ [namespace]: translations
18
+ };
19
+ }
20
+ return result;
21
+ }
22
+ };
23
+ }
24
+ function mergeExtensionTranslations(base, extensions) {
25
+ const result = { ...base };
26
+ for (const ext of extensions) {
27
+ for (const [key, value] of Object.entries(ext)) {
28
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
29
+ result[key] = {
30
+ ...result[key] || {},
31
+ ...value
32
+ };
33
+ } else {
34
+ result[key] = value;
35
+ }
36
+ }
37
+ }
38
+ return result;
39
+ }
40
+ function createNamespacedT(t, namespace) {
41
+ return (key, params) => {
42
+ return t(`${namespace}.${key}`, params);
43
+ };
44
+ }
45
+ function createTypedExtensionT(t, namespace) {
46
+ return (key, params) => t(`${namespace}.${key}`, params);
47
+ }
48
+
49
+ exports.createExtensionI18n = createExtensionI18n;
50
+ exports.createNamespacedT = createNamespacedT;
51
+ exports.createTypedExtensionT = createTypedExtensionT;
52
+ exports.mergeExtensionTranslations = mergeExtensionTranslations;
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Extension I18n Types
3
+ *
4
+ * Base types for extension localization.
5
+ * Each extension defines its own translation structure.
6
+ */
7
+ /**
8
+ * Recursively generates dot-notation paths for nested object types
9
+ * Re-exported from @djangocfg/i18n for convenience
10
+ */
11
+ type PathKeys<T, Prefix extends string = '', Depth extends number[] = []> = Depth['length'] extends 10 ? never : T extends string ? Prefix : T extends object ? {
12
+ [K in keyof T & string]: PathKeys<T[K], Prefix extends '' ? K : `${Prefix}.${K}`, [
13
+ ...Depth,
14
+ 0
15
+ ]>;
16
+ }[keyof T & string] : never;
17
+ /**
18
+ * Generates extension keys with namespace prefix
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * type LeadsTranslations = {
23
+ * form: { title: string; submit: string }
24
+ * }
25
+ *
26
+ * type Keys = ExtensionKeys<'leads', LeadsTranslations>
27
+ * // = "leads.form.title" | "leads.form.submit"
28
+ * ```
29
+ */
30
+ type ExtensionKeys<Namespace extends string, T extends object> = PathKeys<T> extends infer K ? K extends string ? `${Namespace}.${K}` : never : never;
31
+ /**
32
+ * Type-safe translation function for an extension
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * type LeadsT = ExtensionTranslationFn<'leads', LeadsTranslations>
37
+ *
38
+ * const t: LeadsT = useT() as LeadsT
39
+ * t('leads.form.title') // OK
40
+ * t('leads.form.typo') // Error
41
+ * ```
42
+ */
43
+ type ExtensionTranslationFn<Namespace extends string, T extends object> = {
44
+ (key: ExtensionKeys<Namespace, T>, params?: Record<string, string | number>): string;
45
+ };
46
+ /**
47
+ * Supported locale codes for extensions
48
+ */
49
+ type ExtensionLocale = 'en' | 'ru' | 'ko' | string;
50
+ /**
51
+ * Base structure for extension translations
52
+ * Extensions can use any object structure for their translations
53
+ */
54
+ interface ExtensionTranslations {
55
+ }
56
+ /**
57
+ * Extension i18n configuration
58
+ */
59
+ interface ExtensionI18nConfig<T extends object> {
60
+ /** Extension namespace (e.g., 'payments', 'support') */
61
+ namespace: string;
62
+ /** Default locale */
63
+ defaultLocale: ExtensionLocale;
64
+ /** Translations by locale */
65
+ locales: Record<ExtensionLocale, T>;
66
+ }
67
+ /**
68
+ * Extension i18n instance returned by createExtensionI18n
69
+ */
70
+ interface ExtensionI18n<T extends object> {
71
+ /** Extension namespace */
72
+ namespace: string;
73
+ /** Default locale */
74
+ defaultLocale: ExtensionLocale;
75
+ /** Get translations for a specific locale */
76
+ getTranslations: (locale: ExtensionLocale) => T;
77
+ /** Get all translations (for merging with app i18n) */
78
+ getAllTranslations: () => Record<ExtensionLocale, {
79
+ [namespace: string]: T;
80
+ }>;
81
+ /** Available locales */
82
+ locales: ExtensionLocale[];
83
+ }
84
+ /**
85
+ * Deep partial type for translation overrides
86
+ */
87
+ type DeepPartial<T> = T extends object ? {
88
+ [P in keyof T]?: DeepPartial<T[P]>;
89
+ } : T;
90
+
91
+ /**
92
+ * Creates an extension i18n instance
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * // In ext-payments/src/i18n/index.ts
97
+ * import { createExtensionI18n } from '@djangocfg/ext-base/i18n';
98
+ * import { en } from './locales/en';
99
+ * import { ru } from './locales/ru';
100
+ * import { ko } from './locales/ko';
101
+ *
102
+ * export const paymentsI18n = createExtensionI18n({
103
+ * namespace: 'payments',
104
+ * defaultLocale: 'en',
105
+ * locales: { en, ru, ko },
106
+ * });
107
+ *
108
+ * // Export for app to merge
109
+ * export const paymentsTranslations = paymentsI18n.getAllTranslations();
110
+ * ```
111
+ */
112
+ declare function createExtensionI18n<T extends object>(config: ExtensionI18nConfig<T>): ExtensionI18n<T>;
113
+ /**
114
+ * Merges extension translations into app translations
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * // In app's i18n setup
119
+ * import { en, ru, ko, mergeTranslations } from '@djangocfg/i18n';
120
+ * import { paymentsTranslations } from '@djangocfg/ext-payments/i18n';
121
+ * import { supportTranslations } from '@djangocfg/ext-support/i18n';
122
+ *
123
+ * const appTranslations = {
124
+ * en: mergeExtensionTranslations(en, [
125
+ * paymentsTranslations.en,
126
+ * supportTranslations.en,
127
+ * ]),
128
+ * ru: mergeExtensionTranslations(ru, [
129
+ * paymentsTranslations.ru,
130
+ * supportTranslations.ru,
131
+ * ]),
132
+ * };
133
+ * ```
134
+ */
135
+ declare function mergeExtensionTranslations<T extends Record<string, any>>(base: T, extensions: Array<Record<string, any>>): T;
136
+ /**
137
+ * Creates a namespaced translation getter
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * // In extension component
142
+ * const t = useT(); // from @djangocfg/i18n
143
+ * const pt = createNamespacedT(t, 'payments');
144
+ *
145
+ * // Instead of t('payments.balance.available')
146
+ * // You can use pt('balance.available')
147
+ * ```
148
+ */
149
+ declare function createNamespacedT(t: (key: string, params?: Record<string, string | number>) => string, namespace: string): (key: string, params?: Record<string, string | number>) => string;
150
+ /**
151
+ * Creates a type-safe translation function for an extension
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * // In ext-leads component
156
+ * import { createTypedExtensionT } from '@djangocfg/ext-base/i18n';
157
+ * import { useT } from '@djangocfg/i18n';
158
+ * import type { LeadsTranslations } from '../i18n/types';
159
+ *
160
+ * function ContactForm() {
161
+ * const t = useT();
162
+ * const lt = createTypedExtensionT<'leads', LeadsTranslations>(t, 'leads');
163
+ *
164
+ * return <span>{lt('form.title')}</span>; // Type-safe!
165
+ * // lt('form.typo') // Compile error!
166
+ * }
167
+ * ```
168
+ */
169
+ declare function createTypedExtensionT<Namespace extends string, T extends object>(t: (key: string, params?: Record<string, string | number>) => string, namespace: Namespace): <K extends PathKeys<T>>(key: K, params?: Record<string, string | number>) => string;
170
+ /**
171
+ * Type helper for creating extension key types
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * // In ext-leads/src/i18n/types.ts
176
+ * import type { CreateExtensionKeys } from '@djangocfg/ext-base/i18n';
177
+ *
178
+ * export interface LeadsTranslations {
179
+ * form: { title: string; submit: string }
180
+ * success: { title: string }
181
+ * }
182
+ *
183
+ * // Type of all valid keys: "leads.form.title" | "leads.form.submit" | "leads.success.title"
184
+ * export type LeadsKeys = CreateExtensionKeys<'leads', LeadsTranslations>;
185
+ * ```
186
+ */
187
+ type CreateExtensionKeys<Namespace extends string, T extends object> = ExtensionKeys<Namespace, T>;
188
+
189
+ export { type CreateExtensionKeys, type DeepPartial, type ExtensionI18n, type ExtensionI18nConfig, type ExtensionKeys, type ExtensionLocale, type ExtensionTranslationFn, type ExtensionTranslations, type PathKeys, createExtensionI18n, createNamespacedT, createTypedExtensionT, mergeExtensionTranslations };
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Extension I18n Types
3
+ *
4
+ * Base types for extension localization.
5
+ * Each extension defines its own translation structure.
6
+ */
7
+ /**
8
+ * Recursively generates dot-notation paths for nested object types
9
+ * Re-exported from @djangocfg/i18n for convenience
10
+ */
11
+ type PathKeys<T, Prefix extends string = '', Depth extends number[] = []> = Depth['length'] extends 10 ? never : T extends string ? Prefix : T extends object ? {
12
+ [K in keyof T & string]: PathKeys<T[K], Prefix extends '' ? K : `${Prefix}.${K}`, [
13
+ ...Depth,
14
+ 0
15
+ ]>;
16
+ }[keyof T & string] : never;
17
+ /**
18
+ * Generates extension keys with namespace prefix
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * type LeadsTranslations = {
23
+ * form: { title: string; submit: string }
24
+ * }
25
+ *
26
+ * type Keys = ExtensionKeys<'leads', LeadsTranslations>
27
+ * // = "leads.form.title" | "leads.form.submit"
28
+ * ```
29
+ */
30
+ type ExtensionKeys<Namespace extends string, T extends object> = PathKeys<T> extends infer K ? K extends string ? `${Namespace}.${K}` : never : never;
31
+ /**
32
+ * Type-safe translation function for an extension
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * type LeadsT = ExtensionTranslationFn<'leads', LeadsTranslations>
37
+ *
38
+ * const t: LeadsT = useT() as LeadsT
39
+ * t('leads.form.title') // OK
40
+ * t('leads.form.typo') // Error
41
+ * ```
42
+ */
43
+ type ExtensionTranslationFn<Namespace extends string, T extends object> = {
44
+ (key: ExtensionKeys<Namespace, T>, params?: Record<string, string | number>): string;
45
+ };
46
+ /**
47
+ * Supported locale codes for extensions
48
+ */
49
+ type ExtensionLocale = 'en' | 'ru' | 'ko' | string;
50
+ /**
51
+ * Base structure for extension translations
52
+ * Extensions can use any object structure for their translations
53
+ */
54
+ interface ExtensionTranslations {
55
+ }
56
+ /**
57
+ * Extension i18n configuration
58
+ */
59
+ interface ExtensionI18nConfig<T extends object> {
60
+ /** Extension namespace (e.g., 'payments', 'support') */
61
+ namespace: string;
62
+ /** Default locale */
63
+ defaultLocale: ExtensionLocale;
64
+ /** Translations by locale */
65
+ locales: Record<ExtensionLocale, T>;
66
+ }
67
+ /**
68
+ * Extension i18n instance returned by createExtensionI18n
69
+ */
70
+ interface ExtensionI18n<T extends object> {
71
+ /** Extension namespace */
72
+ namespace: string;
73
+ /** Default locale */
74
+ defaultLocale: ExtensionLocale;
75
+ /** Get translations for a specific locale */
76
+ getTranslations: (locale: ExtensionLocale) => T;
77
+ /** Get all translations (for merging with app i18n) */
78
+ getAllTranslations: () => Record<ExtensionLocale, {
79
+ [namespace: string]: T;
80
+ }>;
81
+ /** Available locales */
82
+ locales: ExtensionLocale[];
83
+ }
84
+ /**
85
+ * Deep partial type for translation overrides
86
+ */
87
+ type DeepPartial<T> = T extends object ? {
88
+ [P in keyof T]?: DeepPartial<T[P]>;
89
+ } : T;
90
+
91
+ /**
92
+ * Creates an extension i18n instance
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * // In ext-payments/src/i18n/index.ts
97
+ * import { createExtensionI18n } from '@djangocfg/ext-base/i18n';
98
+ * import { en } from './locales/en';
99
+ * import { ru } from './locales/ru';
100
+ * import { ko } from './locales/ko';
101
+ *
102
+ * export const paymentsI18n = createExtensionI18n({
103
+ * namespace: 'payments',
104
+ * defaultLocale: 'en',
105
+ * locales: { en, ru, ko },
106
+ * });
107
+ *
108
+ * // Export for app to merge
109
+ * export const paymentsTranslations = paymentsI18n.getAllTranslations();
110
+ * ```
111
+ */
112
+ declare function createExtensionI18n<T extends object>(config: ExtensionI18nConfig<T>): ExtensionI18n<T>;
113
+ /**
114
+ * Merges extension translations into app translations
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * // In app's i18n setup
119
+ * import { en, ru, ko, mergeTranslations } from '@djangocfg/i18n';
120
+ * import { paymentsTranslations } from '@djangocfg/ext-payments/i18n';
121
+ * import { supportTranslations } from '@djangocfg/ext-support/i18n';
122
+ *
123
+ * const appTranslations = {
124
+ * en: mergeExtensionTranslations(en, [
125
+ * paymentsTranslations.en,
126
+ * supportTranslations.en,
127
+ * ]),
128
+ * ru: mergeExtensionTranslations(ru, [
129
+ * paymentsTranslations.ru,
130
+ * supportTranslations.ru,
131
+ * ]),
132
+ * };
133
+ * ```
134
+ */
135
+ declare function mergeExtensionTranslations<T extends Record<string, any>>(base: T, extensions: Array<Record<string, any>>): T;
136
+ /**
137
+ * Creates a namespaced translation getter
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * // In extension component
142
+ * const t = useT(); // from @djangocfg/i18n
143
+ * const pt = createNamespacedT(t, 'payments');
144
+ *
145
+ * // Instead of t('payments.balance.available')
146
+ * // You can use pt('balance.available')
147
+ * ```
148
+ */
149
+ declare function createNamespacedT(t: (key: string, params?: Record<string, string | number>) => string, namespace: string): (key: string, params?: Record<string, string | number>) => string;
150
+ /**
151
+ * Creates a type-safe translation function for an extension
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * // In ext-leads component
156
+ * import { createTypedExtensionT } from '@djangocfg/ext-base/i18n';
157
+ * import { useT } from '@djangocfg/i18n';
158
+ * import type { LeadsTranslations } from '../i18n/types';
159
+ *
160
+ * function ContactForm() {
161
+ * const t = useT();
162
+ * const lt = createTypedExtensionT<'leads', LeadsTranslations>(t, 'leads');
163
+ *
164
+ * return <span>{lt('form.title')}</span>; // Type-safe!
165
+ * // lt('form.typo') // Compile error!
166
+ * }
167
+ * ```
168
+ */
169
+ declare function createTypedExtensionT<Namespace extends string, T extends object>(t: (key: string, params?: Record<string, string | number>) => string, namespace: Namespace): <K extends PathKeys<T>>(key: K, params?: Record<string, string | number>) => string;
170
+ /**
171
+ * Type helper for creating extension key types
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * // In ext-leads/src/i18n/types.ts
176
+ * import type { CreateExtensionKeys } from '@djangocfg/ext-base/i18n';
177
+ *
178
+ * export interface LeadsTranslations {
179
+ * form: { title: string; submit: string }
180
+ * success: { title: string }
181
+ * }
182
+ *
183
+ * // Type of all valid keys: "leads.form.title" | "leads.form.submit" | "leads.success.title"
184
+ * export type LeadsKeys = CreateExtensionKeys<'leads', LeadsTranslations>;
185
+ * ```
186
+ */
187
+ type CreateExtensionKeys<Namespace extends string, T extends object> = ExtensionKeys<Namespace, T>;
188
+
189
+ export { type CreateExtensionKeys, type DeepPartial, type ExtensionI18n, type ExtensionI18nConfig, type ExtensionKeys, type ExtensionLocale, type ExtensionTranslationFn, type ExtensionTranslations, type PathKeys, createExtensionI18n, createNamespacedT, createTypedExtensionT, mergeExtensionTranslations };
package/dist/i18n.js ADDED
@@ -0,0 +1,49 @@
1
+ import './chunk-3RG5ZIWI.js';
2
+
3
+ // src/i18n/utils.ts
4
+ function createExtensionI18n(config) {
5
+ const { namespace, defaultLocale, locales } = config;
6
+ return {
7
+ namespace,
8
+ defaultLocale,
9
+ locales: Object.keys(locales),
10
+ getTranslations(locale) {
11
+ return locales[locale] ?? locales[defaultLocale];
12
+ },
13
+ getAllTranslations() {
14
+ const result = {};
15
+ for (const [locale, translations] of Object.entries(locales)) {
16
+ result[locale] = {
17
+ [namespace]: translations
18
+ };
19
+ }
20
+ return result;
21
+ }
22
+ };
23
+ }
24
+ function mergeExtensionTranslations(base, extensions) {
25
+ const result = { ...base };
26
+ for (const ext of extensions) {
27
+ for (const [key, value] of Object.entries(ext)) {
28
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
29
+ result[key] = {
30
+ ...result[key] || {},
31
+ ...value
32
+ };
33
+ } else {
34
+ result[key] = value;
35
+ }
36
+ }
37
+ }
38
+ return result;
39
+ }
40
+ function createNamespacedT(t, namespace) {
41
+ return (key, params) => {
42
+ return t(`${namespace}.${key}`, params);
43
+ };
44
+ }
45
+ function createTypedExtensionT(t, namespace) {
46
+ return (key, params) => t(`${namespace}.${key}`, params);
47
+ }
48
+
49
+ export { createExtensionI18n, createNamespacedT, createTypedExtensionT, mergeExtensionTranslations };
package/dist/index.cjs CHANGED
@@ -25,7 +25,7 @@ var EXTENSION_CATEGORIES = [
25
25
  // package.json
26
26
  var package_default = {
27
27
  name: "@djangocfg/ext-base",
28
- version: "1.0.16",
28
+ version: "1.0.17",
29
29
  description: "Base utilities and common code for DjangoCFG extensions",
30
30
  keywords: [
31
31
  "django",
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { EXTENSION_CATEGORIES, createExtensionConfig, createExtensionError, createExtensionLogger, extensionConfig, formatErrorMessage, handleExtensionError, isExtensionError } from './chunk-RKB5BOPI.js';
2
- export { createExtensionAPI, getApiUrl, getSharedAuthStorage, initializeExtensionAPI, isDevelopment, isProduction, isStaticBuild } from './chunk-555TSGMD.js';
1
+ export { EXTENSION_CATEGORIES, createExtensionConfig, createExtensionError, createExtensionLogger, extensionConfig, formatErrorMessage, handleExtensionError, isExtensionError } from './chunk-AU2RS5WG.js';
2
+ export { createExtensionAPI, getApiUrl, getSharedAuthStorage, initializeExtensionAPI, isDevelopment, isProduction, isStaticBuild } from './chunk-Q4JJNC2D.js';
3
3
  import './chunk-3RG5ZIWI.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ext-base",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "Base utilities and common code for DjangoCFG extensions",
5
5
  "keywords": [
6
6
  "django",
@@ -49,6 +49,11 @@
49
49
  "types": "./dist/api.d.ts",
50
50
  "import": "./dist/api.js",
51
51
  "require": "./dist/api.cjs"
52
+ },
53
+ "./i18n": {
54
+ "types": "./dist/i18n.d.ts",
55
+ "import": "./dist/i18n.js",
56
+ "require": "./dist/i18n.cjs"
52
57
  }
53
58
  },
54
59
  "files": [
@@ -73,7 +78,7 @@
73
78
  "cli:info": "tsx src/cli/index.ts info"
74
79
  },
75
80
  "peerDependencies": {
76
- "@djangocfg/api": "^2.1.109",
81
+ "@djangocfg/api": "^2.1.111",
77
82
  "consola": "^3.4.2",
78
83
  "react": "^19",
79
84
  "react-dom": "^19",
@@ -86,8 +91,8 @@
86
91
  "prompts": "^2.4.2"
87
92
  },
88
93
  "devDependencies": {
89
- "@djangocfg/api": "^2.1.109",
90
- "@djangocfg/typescript-config": "^2.1.109",
94
+ "@djangocfg/api": "^2.1.111",
95
+ "@djangocfg/typescript-config": "^2.1.111",
91
96
  "@types/node": "^24.7.2",
92
97
  "@types/prompts": "^2.4.9",
93
98
  "@types/react": "^19.0.0",
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Extension I18n
3
+ *
4
+ * Base i18n infrastructure for DjangoCFG extensions.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * // Creating extension translations (in ext-payments/src/i18n/index.ts)
9
+ * import { createExtensionI18n } from '@djangocfg/ext-base/i18n';
10
+ *
11
+ * export const paymentsI18n = createExtensionI18n({
12
+ * namespace: 'payments',
13
+ * defaultLocale: 'en',
14
+ * locales: {
15
+ * en: { balance: { available: 'Available Balance' } },
16
+ * ru: { balance: { available: 'Доступный баланс' } },
17
+ * },
18
+ * });
19
+ *
20
+ * export const paymentsTranslations = paymentsI18n.getAllTranslations();
21
+ * ```
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * // Merging in app (in apps/web/providers/I18nProvider.tsx)
26
+ * import { mergeExtensionTranslations } from '@djangocfg/ext-base/i18n';
27
+ * import { en, ru } from '@djangocfg/i18n';
28
+ * import { paymentsTranslations } from '@djangocfg/ext-payments/i18n';
29
+ *
30
+ * const translations = {
31
+ * en: mergeExtensionTranslations(en, [paymentsTranslations.en]),
32
+ * ru: mergeExtensionTranslations(ru, [paymentsTranslations.ru]),
33
+ * };
34
+ * ```
35
+ */
36
+
37
+ // Types
38
+ export type {
39
+ ExtensionLocale,
40
+ ExtensionTranslations,
41
+ ExtensionI18nConfig,
42
+ ExtensionI18n,
43
+ DeepPartial,
44
+ // Type-safe keys
45
+ PathKeys,
46
+ ExtensionKeys,
47
+ ExtensionTranslationFn,
48
+ } from './types';
49
+
50
+ // Utils
51
+ export {
52
+ createExtensionI18n,
53
+ mergeExtensionTranslations,
54
+ createNamespacedT,
55
+ createTypedExtensionT,
56
+ } from './utils';
57
+
58
+ export type { CreateExtensionKeys } from './utils';
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Extension I18n Types
3
+ *
4
+ * Base types for extension localization.
5
+ * Each extension defines its own translation structure.
6
+ */
7
+
8
+ /**
9
+ * Recursively generates dot-notation paths for nested object types
10
+ * Re-exported from @djangocfg/i18n for convenience
11
+ */
12
+ export type PathKeys<T, Prefix extends string = '', Depth extends number[] = []> =
13
+ Depth['length'] extends 10
14
+ ? never
15
+ : T extends string
16
+ ? Prefix
17
+ : T extends object
18
+ ? {
19
+ [K in keyof T & string]: PathKeys<
20
+ T[K],
21
+ Prefix extends '' ? K : `${Prefix}.${K}`,
22
+ [...Depth, 0]
23
+ >
24
+ }[keyof T & string]
25
+ : never;
26
+
27
+ /**
28
+ * Generates extension keys with namespace prefix
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * type LeadsTranslations = {
33
+ * form: { title: string; submit: string }
34
+ * }
35
+ *
36
+ * type Keys = ExtensionKeys<'leads', LeadsTranslations>
37
+ * // = "leads.form.title" | "leads.form.submit"
38
+ * ```
39
+ */
40
+ export type ExtensionKeys<Namespace extends string, T extends object> =
41
+ PathKeys<T> extends infer K
42
+ ? K extends string
43
+ ? `${Namespace}.${K}`
44
+ : never
45
+ : never;
46
+
47
+ /**
48
+ * Type-safe translation function for an extension
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * type LeadsT = ExtensionTranslationFn<'leads', LeadsTranslations>
53
+ *
54
+ * const t: LeadsT = useT() as LeadsT
55
+ * t('leads.form.title') // OK
56
+ * t('leads.form.typo') // Error
57
+ * ```
58
+ */
59
+ export type ExtensionTranslationFn<Namespace extends string, T extends object> = {
60
+ (key: ExtensionKeys<Namespace, T>, params?: Record<string, string | number>): string;
61
+ };
62
+
63
+ /**
64
+ * Supported locale codes for extensions
65
+ */
66
+ export type ExtensionLocale = 'en' | 'ru' | 'ko' | string;
67
+
68
+ /**
69
+ * Base structure for extension translations
70
+ * Extensions can use any object structure for their translations
71
+ */
72
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
73
+ export interface ExtensionTranslations {}
74
+
75
+ /**
76
+ * Extension i18n configuration
77
+ */
78
+ export interface ExtensionI18nConfig<T extends object> {
79
+ /** Extension namespace (e.g., 'payments', 'support') */
80
+ namespace: string;
81
+ /** Default locale */
82
+ defaultLocale: ExtensionLocale;
83
+ /** Translations by locale */
84
+ locales: Record<ExtensionLocale, T>;
85
+ }
86
+
87
+ /**
88
+ * Extension i18n instance returned by createExtensionI18n
89
+ */
90
+ export interface ExtensionI18n<T extends object> {
91
+ /** Extension namespace */
92
+ namespace: string;
93
+ /** Default locale */
94
+ defaultLocale: ExtensionLocale;
95
+ /** Get translations for a specific locale */
96
+ getTranslations: (locale: ExtensionLocale) => T;
97
+ /** Get all translations (for merging with app i18n) */
98
+ getAllTranslations: () => Record<ExtensionLocale, { [namespace: string]: T }>;
99
+ /** Available locales */
100
+ locales: ExtensionLocale[];
101
+ }
102
+
103
+ /**
104
+ * Deep partial type for translation overrides
105
+ */
106
+ export type DeepPartial<T> = T extends object
107
+ ? { [P in keyof T]?: DeepPartial<T[P]> }
108
+ : T;
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Extension I18n Utilities
3
+ *
4
+ * Helper functions for creating and managing extension translations.
5
+ */
6
+
7
+ import type {
8
+ ExtensionI18n,
9
+ ExtensionI18nConfig,
10
+ ExtensionLocale,
11
+ ExtensionKeys,
12
+ } from './types';
13
+
14
+ /**
15
+ * Creates an extension i18n instance
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // In ext-payments/src/i18n/index.ts
20
+ * import { createExtensionI18n } from '@djangocfg/ext-base/i18n';
21
+ * import { en } from './locales/en';
22
+ * import { ru } from './locales/ru';
23
+ * import { ko } from './locales/ko';
24
+ *
25
+ * export const paymentsI18n = createExtensionI18n({
26
+ * namespace: 'payments',
27
+ * defaultLocale: 'en',
28
+ * locales: { en, ru, ko },
29
+ * });
30
+ *
31
+ * // Export for app to merge
32
+ * export const paymentsTranslations = paymentsI18n.getAllTranslations();
33
+ * ```
34
+ */
35
+ export function createExtensionI18n<T extends object>(
36
+ config: ExtensionI18nConfig<T>
37
+ ): ExtensionI18n<T> {
38
+ const { namespace, defaultLocale, locales } = config;
39
+
40
+ return {
41
+ namespace,
42
+ defaultLocale,
43
+ locales: Object.keys(locales) as ExtensionLocale[],
44
+
45
+ getTranslations(locale: ExtensionLocale): T {
46
+ return locales[locale] ?? locales[defaultLocale];
47
+ },
48
+
49
+ getAllTranslations(): Record<ExtensionLocale, { [ns: string]: T }> {
50
+ const result: Record<ExtensionLocale, { [ns: string]: T }> = {} as any;
51
+
52
+ for (const [locale, translations] of Object.entries(locales)) {
53
+ result[locale as ExtensionLocale] = {
54
+ [namespace]: translations as T,
55
+ };
56
+ }
57
+
58
+ return result;
59
+ },
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Merges extension translations into app translations
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * // In app's i18n setup
69
+ * import { en, ru, ko, mergeTranslations } from '@djangocfg/i18n';
70
+ * import { paymentsTranslations } from '@djangocfg/ext-payments/i18n';
71
+ * import { supportTranslations } from '@djangocfg/ext-support/i18n';
72
+ *
73
+ * const appTranslations = {
74
+ * en: mergeExtensionTranslations(en, [
75
+ * paymentsTranslations.en,
76
+ * supportTranslations.en,
77
+ * ]),
78
+ * ru: mergeExtensionTranslations(ru, [
79
+ * paymentsTranslations.ru,
80
+ * supportTranslations.ru,
81
+ * ]),
82
+ * };
83
+ * ```
84
+ */
85
+ export function mergeExtensionTranslations<T extends Record<string, any>>(
86
+ base: T,
87
+ extensions: Array<Record<string, any>>
88
+ ): T {
89
+ const result = { ...base };
90
+
91
+ for (const ext of extensions) {
92
+ for (const [key, value] of Object.entries(ext)) {
93
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
94
+ (result as any)[key] = {
95
+ ...((result as any)[key] || {}),
96
+ ...value,
97
+ };
98
+ } else {
99
+ (result as any)[key] = value;
100
+ }
101
+ }
102
+ }
103
+
104
+ return result;
105
+ }
106
+
107
+ /**
108
+ * Creates a namespaced translation getter
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * // In extension component
113
+ * const t = useT(); // from @djangocfg/i18n
114
+ * const pt = createNamespacedT(t, 'payments');
115
+ *
116
+ * // Instead of t('payments.balance.available')
117
+ * // You can use pt('balance.available')
118
+ * ```
119
+ */
120
+ export function createNamespacedT(
121
+ t: (key: string, params?: Record<string, string | number>) => string,
122
+ namespace: string
123
+ ): (key: string, params?: Record<string, string | number>) => string {
124
+ return (key: string, params?: Record<string, string | number>) => {
125
+ return t(`${namespace}.${key}`, params);
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Creates a type-safe translation function for an extension
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * // In ext-leads component
135
+ * import { createTypedExtensionT } from '@djangocfg/ext-base/i18n';
136
+ * import { useT } from '@djangocfg/i18n';
137
+ * import type { LeadsTranslations } from '../i18n/types';
138
+ *
139
+ * function ContactForm() {
140
+ * const t = useT();
141
+ * const lt = createTypedExtensionT<'leads', LeadsTranslations>(t, 'leads');
142
+ *
143
+ * return <span>{lt('form.title')}</span>; // Type-safe!
144
+ * // lt('form.typo') // Compile error!
145
+ * }
146
+ * ```
147
+ */
148
+ export function createTypedExtensionT<
149
+ Namespace extends string,
150
+ T extends object
151
+ >(
152
+ t: (key: string, params?: Record<string, string | number>) => string,
153
+ namespace: Namespace
154
+ ): <K extends import('./types').PathKeys<T>>(
155
+ key: K,
156
+ params?: Record<string, string | number>
157
+ ) => string {
158
+ return (key, params) => t(`${namespace}.${key}`, params);
159
+ }
160
+
161
+ /**
162
+ * Type helper for creating extension key types
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * // In ext-leads/src/i18n/types.ts
167
+ * import type { CreateExtensionKeys } from '@djangocfg/ext-base/i18n';
168
+ *
169
+ * export interface LeadsTranslations {
170
+ * form: { title: string; submit: string }
171
+ * success: { title: string }
172
+ * }
173
+ *
174
+ * // Type of all valid keys: "leads.form.title" | "leads.form.submit" | "leads.success.title"
175
+ * export type LeadsKeys = CreateExtensionKeys<'leads', LeadsTranslations>;
176
+ * ```
177
+ */
178
+ export type CreateExtensionKeys<
179
+ Namespace extends string,
180
+ T extends object
181
+ > = ExtensionKeys<Namespace, T>;