@spfn/cms 0.2.0-beta.5 → 0.2.0-beta.6
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/index.d.ts +7 -1
- package/dist/index.js +2 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -102,7 +102,12 @@ declare function format(template: string, vars: Record<string, string | number>)
|
|
|
102
102
|
* @example
|
|
103
103
|
* ```typescript
|
|
104
104
|
* // labels.ts - Setup once
|
|
105
|
-
*
|
|
105
|
+
* import { getLocale } from '@spfn/cms/actions';
|
|
106
|
+
*
|
|
107
|
+
* export const { api, getLabel, getLabels, format } = createCmsClient(labelsDefinition, {
|
|
108
|
+
* defaultLocale: 'ko',
|
|
109
|
+
* getLocale: () => getLocale('ko'),
|
|
110
|
+
* });
|
|
106
111
|
*
|
|
107
112
|
* // Single section - direct access
|
|
108
113
|
* const label = await getLabel('home');
|
|
@@ -121,6 +126,7 @@ declare function format(template: string, vars: Record<string, string | number>)
|
|
|
121
126
|
declare function createCmsClient<T>(labelsDefinition: T, config: {
|
|
122
127
|
defaultLocale: string;
|
|
123
128
|
fallbackLocale?: string;
|
|
129
|
+
getLocale: () => Promise<string>;
|
|
124
130
|
}): {
|
|
125
131
|
api: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
|
|
126
132
|
getLabelCache: _spfn_core_route.RouteDef<{
|
package/dist/index.js
CHANGED
|
@@ -41,16 +41,6 @@ function createProxy(obj, locale, fallbackLocale) {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// src/actions.ts
|
|
45
|
-
import { cookies } from "next/headers";
|
|
46
|
-
var LOCALE_COOKIE_NAME = "cms-locale";
|
|
47
|
-
var LOCALE_MAX_AGE = 365 * 24 * 60 * 60;
|
|
48
|
-
async function getLocale(defaultLocale) {
|
|
49
|
-
const cookieStore = await cookies();
|
|
50
|
-
const localeCookie = cookieStore.get(LOCALE_COOKIE_NAME);
|
|
51
|
-
return localeCookie?.value ?? defaultLocale ?? "en";
|
|
52
|
-
}
|
|
53
|
-
|
|
54
44
|
// src/lib/helpers.ts
|
|
55
45
|
function setNestedValue(target, path, value) {
|
|
56
46
|
const parts = path.split(".");
|
|
@@ -85,7 +75,7 @@ var cmsLogger = logger.child("@spfn/cms");
|
|
|
85
75
|
var api = createApi();
|
|
86
76
|
function createCmsClient(labelsDefinition, config) {
|
|
87
77
|
async function getLabel(section) {
|
|
88
|
-
const locale = await getLocale(
|
|
78
|
+
const locale = await config.getLocale();
|
|
89
79
|
cmsLogger.debug("getLabel called", {
|
|
90
80
|
section,
|
|
91
81
|
locale,
|
|
@@ -107,7 +97,7 @@ function createCmsClient(labelsDefinition, config) {
|
|
|
107
97
|
return merged[section];
|
|
108
98
|
}
|
|
109
99
|
async function getLabels(sections) {
|
|
110
|
-
const locale = await getLocale(
|
|
100
|
+
const locale = await config.getLocale();
|
|
111
101
|
cmsLogger.debug("getLabels called", {
|
|
112
102
|
sections,
|
|
113
103
|
locale,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/lib/bind-locale.ts","../src/actions.ts","../src/lib/helpers.ts","../src/lib/define-labels.ts"],"sourcesContent":["import { createApi } from \"@spfn/core/nextjs\";\nimport { logger } from \"@spfn/core/logger\";\nimport { type AppRouter } from './server/routes/index';\nimport { bindLocale, type SectionKeys, type BoundLabelSection, type BoundLabelsSections } from './lib/bind-locale';\nimport { getLocale } from './actions';\nimport { setNestedValue } from './lib/helpers';\nimport { format, defineLabelConfig, defineLabels } from './lib/define-labels';\n\nconst cmsLogger = logger.child('@spfn/cms');\n\n/**\n * Default API client (for backward compatibility or when not using labels)\n */\nconst api = createApi<AppRouter>();\n\n/**\n * Create CMS client with API, label getters, and format utility\n *\n * @param labelsDefinition - Labels defined using defineLabels()\n * @param config - Label config from defineLabelConfig()\n * @returns API client, getLabel (single), getLabels (multiple), and format utility\n *\n * @example\n * ```typescript\n * // labels.ts - Setup once\n * export const { api, getLabel, getLabels, format } = createCmsClient(labelsDefinition, labelConfig);\n *\n * // Single section - direct access\n * const label = await getLabel('home');\n * label.hero.title // \"Hello\" (no section name!)\n *\n * // Multiple sections - with section names\n * const labels = await getLabels(['home', 'about']);\n * labels.home.hero.title // \"Hello\"\n * labels.about.title // \"About Us\"\n *\n * // With template variables\n * const greeting = label.hero.greeting; // \"Hello {name}\"\n * format(greeting, { name: \"John\" }); // \"Hello John\"\n * ```\n */\nexport function createCmsClient<T>(\n labelsDefinition: T,\n config: { defaultLocale: string; fallbackLocale?: string }\n)\n{\n /**\n * Get a single section's labels (without section name wrapper)\n *\n * @param section - Section name to fetch\n * @returns Labels for the section, directly accessible\n *\n * @example\n * ```typescript\n * const label = await getLabel('signup');\n * label.title // Direct access\n * label.userName\n * ```\n */\n async function getLabel<K extends SectionKeys<T>>(section: K): Promise<BoundLabelSection<T, K>>\n {\n // Auto-detect locale from cookie, fallback to config.defaultLocale\n const locale = await getLocale(config.defaultLocale);\n\n cmsLogger.debug('getLabel called', {\n section,\n locale,\n defaultLocale: config.defaultLocale,\n fallbackLocale: config.fallbackLocale,\n });\n\n // 1. Fetch from published_cache\n const cache = await api.getLabelCache.call({\n query: {\n sections: [section as string],\n locale\n }\n });\n\n // 2. Filter only requested section\n const filteredLabels: any = {};\n if (section in (labelsDefinition as any))\n {\n filteredLabels[section] = (labelsDefinition as any)[section];\n }\n\n // 3. Generate defaults with locale binding\n const defaults = bindLocale(filteredLabels, locale, config.fallbackLocale);\n\n // 4. Merge: cache takes priority, fallback to defaults\n const merged = deepMergeCache(defaults, cache, locale);\n\n // 5. Return only the section content (without section name)\n return merged[section] as BoundLabelSection<T, K>;\n }\n\n /**\n * Get multiple sections' labels (with section names as keys)\n *\n * @param sections - Array of section names to fetch\n * @returns Object with section names as keys\n *\n * @example\n * ```typescript\n * const labels = await getLabels(['home', 'about']);\n * labels.home.title\n * labels.about.description\n * ```\n */\n async function getLabels<K extends SectionKeys<T>>(sections: readonly K[]): Promise<BoundLabelsSections<T, K>>\n {\n // Auto-detect locale from cookie, fallback to config.defaultLocale\n const locale = await getLocale(config.defaultLocale);\n\n cmsLogger.debug('getLabels called', {\n sections,\n locale,\n defaultLocale: config.defaultLocale,\n fallbackLocale: config.fallbackLocale,\n availableDefinitionKeys: Object.keys(labelsDefinition as any),\n });\n\n // 1. Fetch from published_cache\n const cache = await api.getLabelCache.call({\n query: {\n sections: [...sections] as unknown as string[],\n locale\n }\n });\n\n cmsLogger.debug('Fetched from cache', {\n cacheKeys: Object.keys(cache),\n cacheEntryCount: Object.keys(cache).length,\n cacheStructure: Object.entries(cache).map(([key, value]) => ({\n section: key,\n isObject: typeof value === 'object',\n isNull: value === null,\n contentKeys: value && typeof value === 'object' ? Object.keys(value) : [],\n })),\n });\n\n // 2. Filter only requested sections (performance optimization)\n const filteredLabels: any = {};\n for (const section of sections)\n {\n if (section in (labelsDefinition as any))\n {\n filteredLabels[section] = (labelsDefinition as any)[section];\n }\n }\n\n cmsLogger.debug('Filtered sections', {\n requestedSections: sections,\n filteredSections: Object.keys(filteredLabels),\n filteredLabelsStructure: Object.entries(filteredLabels).map(([key, value]) => ({\n section: key,\n hasValue: !!value,\n isObject: typeof value === 'object',\n nestedKeys: value && typeof value === 'object' ? Object.keys(value) : [],\n })),\n });\n\n // 3. Generate defaults with locale binding (only for requested sections)\n const defaults = bindLocale(filteredLabels, locale, config.fallbackLocale);\n\n cmsLogger.debug('Generated defaults with locale binding', {\n defaultsKeys: Object.keys(defaults),\n });\n\n // 4. Merge: cache takes priority, fallback to defaults\n const merged = deepMergeCache(defaults, cache, locale);\n\n cmsLogger.debug('Merged cache and defaults', {\n mergedKeys: Object.keys(merged),\n });\n\n return merged as BoundLabelsSections<T, K>;\n }\n\n return { api, getLabel, getLabels, format };\n}\n\n/**\n * Deep merge cache into defaults\n */\nfunction deepMergeCache(defaults: any, cache: Record<string, any>, locale: string): any\n{\n const result = { ...defaults };\n\n cmsLogger.debug('deepMergeCache: Starting merge', {\n cacheEntries: Object.keys(cache).length,\n locale,\n });\n\n for (const [section, content] of Object.entries(cache))\n {\n if (!content || typeof content !== 'object')\n {\n cmsLogger.debug('deepMergeCache: Skipping invalid content', { section });\n continue;\n }\n\n const contentKeys = Object.keys(content);\n cmsLogger.debug('deepMergeCache: Processing section', {\n section,\n labelCount: contentKeys.length,\n });\n\n for (const [flatKey, value] of Object.entries(content))\n {\n // Extract locale-specific value from LabelValue format\n let extractedValue: any;\n\n if (value && typeof value === 'object' && 'content' in value)\n {\n extractedValue = (value as any).content;\n cmsLogger.debug('deepMergeCache: Extracted from content field', {\n flatKey,\n hasContent: true,\n });\n }\n else if (value && typeof value === 'object' && locale in value)\n {\n extractedValue = (value as any)[locale];\n cmsLogger.debug('deepMergeCache: Extracted from locale field', {\n flatKey,\n locale,\n });\n }\n else\n {\n extractedValue = value;\n cmsLogger.debug('deepMergeCache: Using raw value', {\n flatKey,\n valueType: typeof value,\n });\n }\n\n // Set value using helper function\n setNestedValue(result, flatKey, extractedValue);\n }\n }\n\n cmsLogger.debug('deepMergeCache: Merge completed', {\n resultKeys: Object.keys(result),\n });\n\n return result;\n}\n\n/**\n * Re-export format utility for standalone use\n *\n * @example\n * ```typescript\n * import { format } from '@spfn/cms/api-client';\n *\n * const text = \"Hello {name}, you have {count} messages\";\n * format(text, { name: \"John\", count: 5 });\n * // \"Hello John, you have 5 messages\"\n * ```\n */\nexport { format, defineLabelConfig, defineLabels };\n\n/**\n * Re-export types for external use\n */\nexport type { BoundLabels, SectionKeys, BoundLabelSection, BoundLabelsSections } from './lib/bind-locale';","/**\n * Bind locale to labels, returning locale-specific values\n *\n * @example\n * ```ts\n * const labelsDefinition = defineLabels({\n * home: {\n * title: { en: \"Home\", ko: \"홈\" }\n * }\n * });\n *\n * const labels = bindLocale(labelsDefinition, 'ko');\n * labels.home.title // \"홈\"\n * ```\n */\n\n/**\n * Type that converts locale records to strings\n */\nexport type BoundLabels<T> = {\n [K in keyof T]: T[K] extends Record<string, any>\n ? IsLocaleRecord<T[K]> extends true\n ? string\n : BoundLabels<T[K]>\n : T[K];\n};\n\n/**\n * Extract section keys from label definition\n */\nexport type SectionKeys<T> = Extract<keyof T, string>;\n\n/**\n * Get content of a single section (without section name wrapper)\n */\nexport type BoundLabelSection<T, K extends SectionKeys<T>> = BoundLabels<T>[K];\n\n/**\n * Pick specific sections from bound labels (for multiple sections)\n */\nexport type BoundLabelsSections<T, K extends SectionKeys<T>> = Pick<BoundLabels<T>, K>;\n\n/**\n * Check if object is a locale record (has string values only)\n */\ntype IsLocaleRecord<T> = T extends Record<string, string> ? true : false;\n\n/**\n * Check if an object is a locale record at runtime\n */\nfunction isLocaleRecord(obj: any): boolean\n{\n if (!obj || typeof obj !== 'object')\n {\n return false;\n }\n\n const values = Object.values(obj);\n\n // Empty object is not a locale record\n if (values.length === 0)\n {\n return false;\n }\n\n // All values must be strings\n return values.every(v => typeof v === 'string');\n}\n\n/**\n * Bind a locale to label definitions, returning locale-specific values\n *\n * @param labels - Label definitions with locale records\n * @param locale - Locale to bind (e.g., 'en', 'ko')\n * @param fallbackLocale - Optional fallback locale if value not found\n * @returns Labels with locale-specific string values\n *\n * @example\n * ```typescript\n * const labelsDefinition = defineLabels({\n * home: {\n * title: { en: \"Home\", ko: \"홈\" },\n * hero: {\n * title: { en: \"Welcome\", ko: \"환영합니다\" }\n * }\n * }\n * });\n *\n * const labels = bindLocale(labelsDefinition, 'ko');\n * labels.home.title // \"홈\"\n * labels.home.hero.title // \"환영합니다\"\n *\n * // With fallback\n * const labelsEn = bindLocale(labelsDefinition, 'en', 'ko');\n * ```\n */\nexport function bindLocale<T>(\n labels: T,\n locale: string,\n fallbackLocale?: string\n): BoundLabels<T>\n{\n return createProxy(labels, locale, fallbackLocale) as BoundLabels<T>;\n}\n\n/**\n * Create a proxy that intercepts property access and returns locale-specific values\n */\nfunction createProxy(obj: any, locale: string, fallbackLocale?: string): any\n{\n return new Proxy(obj, {\n get(target, prop)\n {\n const value = target[prop];\n\n // If value doesn't exist, return undefined\n if (value === undefined)\n {\n return undefined;\n }\n\n // If this is a locale record, return the locale value\n if (isLocaleRecord(value))\n {\n // Try to get the requested locale\n if (value[locale] !== undefined)\n {\n return value[locale];\n }\n\n // Fallback to fallbackLocale if specified\n if (fallbackLocale && value[fallbackLocale] !== undefined)\n {\n return value[fallbackLocale];\n }\n\n // If locale not found, return first available locale\n const firstLocale = Object.keys(value)[0];\n return value[firstLocale];\n }\n\n // If this is a nested object, wrap it in a proxy\n if (typeof value === 'object' && value !== null)\n {\n return createProxy(value, locale, fallbackLocale);\n }\n\n // Otherwise return as-is\n return value;\n },\n });\n}","\"use server\"\n\nimport { cookies } from 'next/headers';\n\nconst LOCALE_COOKIE_NAME = 'cms-locale';\nconst LOCALE_MAX_AGE = 365 * 24 * 60 * 60; // 1 year\n\n/**\n * Set user's preferred locale in cookie\n *\n * @param locale - Language code (e.g., 'ko', 'en', 'ja')\n */\nexport async function setLocale(locale: string): Promise<void>\n{\n const cookieStore = await cookies();\n\n cookieStore.set(LOCALE_COOKIE_NAME, locale, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: LOCALE_MAX_AGE,\n path: '/',\n });\n}\n\n/**\n * Get user's preferred locale from cookie\n *\n * @param defaultLocale - Default locale from labelConfig.defaultLocale\n * @returns Language code (from cookie, or defaultLocale, or 'en')\n */\nexport async function getLocale(defaultLocale?: string): Promise<string>\n{\n const cookieStore = await cookies();\n const localeCookie = cookieStore.get(LOCALE_COOKIE_NAME);\n\n return localeCookie?.value ?? defaultLocale ?? 'en';\n}","/**\n * CMS Helper Functions\n */\n\nexport type FlatLabel = Record<string, Record<string, string>>;\n\n/**\n * Flatten nested label structure into dot notation\n *\n * @param labels - Nested label object\n * @param prefix - Key prefix for recursion\n * @returns Flattened label structure\n *\n * @example\n * ```typescript\n * const nested = {\n * home: {\n * hero: {\n * title: { en: \"Welcome\", ko: \"환영합니다\" }\n * }\n * }\n * };\n *\n * const flat = flattenLabels(nested);\n * // { \"home.hero.title\": { en: \"Welcome\", ko: \"환영합니다\" } }\n * ```\n */\nexport function flattenLabels<T extends Record<string, any>>(labels: T, prefix = ''): FlatLabel\n{\n const result: FlatLabel = {};\n\n if (!labels || typeof labels !== 'object')\n {\n return result;\n }\n\n const obj = labels as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(obj))\n {\n const newKey = prefix ? `${prefix}.${key}` : key;\n\n if (!value || typeof value !== 'object')\n {\n continue;\n }\n\n const valueObj = value as Record<string, unknown>;\n\n // Check if this is a leaf node (locale values: { en: \"...\", ko: \"...\" })\n const isLeaf = Object.values(valueObj).every(v => typeof v === 'string');\n\n if (isLeaf)\n {\n result[newKey] = valueObj as Record<string, string>;\n }\n else\n {\n // Recursively flatten nested structure\n Object.assign(result, flattenLabels(value, newKey));\n }\n }\n\n return result;\n}\n\n/**\n * Set a value in nested object using dot notation path\n *\n * @param target - Target object to modify\n * @param path - Dot notation path (e.g., \"home.hero.title\")\n * @param value - Value to set\n *\n * @example\n * ```typescript\n * const obj = {};\n * setNestedValue(obj, \"home.hero.title\", \"Welcome\");\n * // obj = { home: { hero: { title: \"Welcome\" } } }\n * ```\n */\nexport function setNestedValue(target: any, path: string, value: any): void\n{\n const parts = path.split('.');\n let current = target;\n\n for (let i = 0; i < parts.length - 1; i++)\n {\n const part = parts[i];\n if (!current[part])\n {\n current[part] = {};\n }\n current = current[part];\n }\n\n // Set the leaf value\n const lastPart = parts[parts.length - 1];\n current[lastPart] = value;\n}\n\n/**\n * Unflatten dot notation keys back to nested structure\n *\n * @param flat - Flattened label object\n * @returns Nested label structure\n *\n * @example\n * ```typescript\n * const flat = {\n * \"home.hero.title\": { en: \"Welcome\", ko: \"환영합니다\" },\n * \"home.hero.subtitle\": { en: \"Subtitle\", ko: \"부제목\" }\n * };\n *\n * const nested = unflattenLabels(flat);\n * // {\n * // home: {\n * // hero: {\n * // title: { en: \"Welcome\", ko: \"환영합니다\" },\n * // subtitle: { en: \"Subtitle\", ko: \"부제목\" }\n * // }\n * // }\n * // }\n * ```\n */\nexport function unflattenLabels(flat: FlatLabel): Record<string, any>\n{\n const result: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(flat))\n {\n setNestedValue(result, key, value);\n }\n\n return result;\n}","/**\n * Defines a type-safe label configuration.\n *\n * @example\n * ```ts\n * export const labelConfig = defineLabelConfig({\n * locales: ['en', 'ar'] as const,\n * defaultLocale: 'en',\n * fallbackLocale: 'en', // Optional\n * });\n *\n * export type LabelConfig = typeof labelConfig;\n * export type AppLocale = typeof labelConfig.locales[number]; // 'en' | 'ar'\n * ```\n */\nexport function defineLabelConfig<const TLocales extends readonly string[]>(config: {\n locales: TLocales;\n defaultLocale: TLocales[number];\n fallbackLocale?: TLocales[number];\n useBrowserLanguage?: boolean;\n})\n{\n return config;\n}\n\n/**\n * Define nested label structure (tRPC-style)\n *\n * @example\n * ```ts\n * export const labels = defineLabels({\n * home: {\n * slogan: { en: \"Welcome\", ko: \"환영합니다\" },\n * hero: {\n * title: { en: \"Hello\", ko: \"안녕하세요\" }\n * }\n * },\n * about: {\n * title: { en: \"About Us\", ko: \"회사 소개\" }\n * }\n * });\n *\n * // Usage\n * labels.home.slogan;\n * labels.home.hero.title;\n * labels.about.title;\n * ```\n */\nexport function defineLabels<const T>(labels: T)\n{\n return labels;\n}\n\nexport function format(template: string, vars: Record<string, string | number>): string\n{\n return template.replace(/\\{(\\w+)}/g, (match, key) =>\n {\n const value = vars[key];\n return value !== undefined ? String(value) : match;\n });\n}"],"mappings":";AAAA,SAAS,iBAAiB;AAC1B,SAAS,cAAc;;;ACiDvB,SAAS,eAAe,KACxB;AACI,MAAI,CAAC,OAAO,OAAO,QAAQ,UAC3B;AACI,WAAO;AAAA,EACX;AAEA,QAAM,SAAS,OAAO,OAAO,GAAG;AAGhC,MAAI,OAAO,WAAW,GACtB;AACI,WAAO;AAAA,EACX;AAGA,SAAO,OAAO,MAAM,OAAK,OAAO,MAAM,QAAQ;AAClD;AA6BO,SAAS,WACZ,QACA,QACA,gBAEJ;AACI,SAAO,YAAY,QAAQ,QAAQ,cAAc;AACrD;AAKA,SAAS,YAAY,KAAU,QAAgB,gBAC/C;AACI,SAAO,IAAI,MAAM,KAAK;AAAA,IAClB,IAAI,QAAQ,MACZ;AACI,YAAM,QAAQ,OAAO,IAAI;AAGzB,UAAI,UAAU,QACd;AACI,eAAO;AAAA,MACX;AAGA,UAAI,eAAe,KAAK,GACxB;AAEI,YAAI,MAAM,MAAM,MAAM,QACtB;AACI,iBAAO,MAAM,MAAM;AAAA,QACvB;AAGA,YAAI,kBAAkB,MAAM,cAAc,MAAM,QAChD;AACI,iBAAO,MAAM,cAAc;AAAA,QAC/B;AAGA,cAAM,cAAc,OAAO,KAAK,KAAK,EAAE,CAAC;AACxC,eAAO,MAAM,WAAW;AAAA,MAC5B;AAGA,UAAI,OAAO,UAAU,YAAY,UAAU,MAC3C;AACI,eAAO,YAAY,OAAO,QAAQ,cAAc;AAAA,MACpD;AAGA,aAAO;AAAA,IACX;AAAA,EACJ,CAAC;AACL;;;ACrJA,SAAS,eAAe;AAExB,IAAM,qBAAqB;AAC3B,IAAM,iBAAiB,MAAM,KAAK,KAAK;AA0BvC,eAAsB,UAAU,eAChC;AACI,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,eAAe,YAAY,IAAI,kBAAkB;AAEvD,SAAO,cAAc,SAAS,iBAAiB;AACnD;;;AC2CO,SAAS,eAAe,QAAa,MAAc,OAC1D;AACI,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,MAAI,UAAU;AAEd,WAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KACtC;AACI,UAAM,OAAO,MAAM,CAAC;AACpB,QAAI,CAAC,QAAQ,IAAI,GACjB;AACI,cAAQ,IAAI,IAAI,CAAC;AAAA,IACrB;AACA,cAAU,QAAQ,IAAI;AAAA,EAC1B;AAGA,QAAM,WAAW,MAAM,MAAM,SAAS,CAAC;AACvC,UAAQ,QAAQ,IAAI;AACxB;;;ACnFO,SAAS,kBAA4D,QAM5E;AACI,SAAO;AACX;AAyBO,SAAS,aAAsB,QACtC;AACI,SAAO;AACX;AAEO,SAAS,OAAO,UAAkB,MACzC;AACI,SAAO,SAAS,QAAQ,aAAa,CAAC,OAAO,QAC7C;AACI,UAAM,QAAQ,KAAK,GAAG;AACtB,WAAO,UAAU,SAAY,OAAO,KAAK,IAAI;AAAA,EACjD,CAAC;AACL;;;AJpDA,IAAM,YAAY,OAAO,MAAM,WAAW;AAK1C,IAAM,MAAM,UAAqB;AA4B1B,SAAS,gBACZ,kBACA,QAEJ;AAcI,iBAAe,SAAmC,SAClD;AAEI,UAAM,SAAS,MAAM,UAAU,OAAO,aAAa;AAEnD,cAAU,MAAM,mBAAmB;AAAA,MAC/B;AAAA,MACA;AAAA,MACA,eAAe,OAAO;AAAA,MACtB,gBAAgB,OAAO;AAAA,IAC3B,CAAC;AAGD,UAAM,QAAQ,MAAM,IAAI,cAAc,KAAK;AAAA,MACvC,OAAO;AAAA,QACH,UAAU,CAAC,OAAiB;AAAA,QAC5B;AAAA,MACJ;AAAA,IACJ,CAAC;AAGD,UAAM,iBAAsB,CAAC;AAC7B,QAAI,WAAY,kBAChB;AACI,qBAAe,OAAO,IAAK,iBAAyB,OAAO;AAAA,IAC/D;AAGA,UAAM,WAAW,WAAW,gBAAgB,QAAQ,OAAO,cAAc;AAGzE,UAAM,SAAS,eAAe,UAAU,OAAO,MAAM;AAGrD,WAAO,OAAO,OAAO;AAAA,EACzB;AAeA,iBAAe,UAAoC,UACnD;AAEI,UAAM,SAAS,MAAM,UAAU,OAAO,aAAa;AAEnD,cAAU,MAAM,oBAAoB;AAAA,MAChC;AAAA,MACA;AAAA,MACA,eAAe,OAAO;AAAA,MACtB,gBAAgB,OAAO;AAAA,MACvB,yBAAyB,OAAO,KAAK,gBAAuB;AAAA,IAChE,CAAC;AAGD,UAAM,QAAQ,MAAM,IAAI,cAAc,KAAK;AAAA,MACvC,OAAO;AAAA,QACH,UAAU,CAAC,GAAG,QAAQ;AAAA,QACtB;AAAA,MACJ;AAAA,IACJ,CAAC;AAED,cAAU,MAAM,sBAAsB;AAAA,MAClC,WAAW,OAAO,KAAK,KAAK;AAAA,MAC5B,iBAAiB,OAAO,KAAK,KAAK,EAAE;AAAA,MACpC,gBAAgB,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,QACzD,SAAS;AAAA,QACT,UAAU,OAAO,UAAU;AAAA,QAC3B,QAAQ,UAAU;AAAA,QAClB,aAAa,SAAS,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI,CAAC;AAAA,MAC5E,EAAE;AAAA,IACN,CAAC;AAGD,UAAM,iBAAsB,CAAC;AAC7B,eAAW,WAAW,UACtB;AACI,UAAI,WAAY,kBAChB;AACI,uBAAe,OAAO,IAAK,iBAAyB,OAAO;AAAA,MAC/D;AAAA,IACJ;AAEA,cAAU,MAAM,qBAAqB;AAAA,MACjC,mBAAmB;AAAA,MACnB,kBAAkB,OAAO,KAAK,cAAc;AAAA,MAC5C,yBAAyB,OAAO,QAAQ,cAAc,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,QAC3E,SAAS;AAAA,QACT,UAAU,CAAC,CAAC;AAAA,QACZ,UAAU,OAAO,UAAU;AAAA,QAC3B,YAAY,SAAS,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI,CAAC;AAAA,MAC3E,EAAE;AAAA,IACN,CAAC;AAGD,UAAM,WAAW,WAAW,gBAAgB,QAAQ,OAAO,cAAc;AAEzE,cAAU,MAAM,0CAA0C;AAAA,MACtD,cAAc,OAAO,KAAK,QAAQ;AAAA,IACtC,CAAC;AAGD,UAAM,SAAS,eAAe,UAAU,OAAO,MAAM;AAErD,cAAU,MAAM,6BAA6B;AAAA,MACzC,YAAY,OAAO,KAAK,MAAM;AAAA,IAClC,CAAC;AAED,WAAO;AAAA,EACX;AAEA,SAAO,EAAE,KAAK,UAAU,WAAW,OAAO;AAC9C;AAKA,SAAS,eAAe,UAAe,OAA4B,QACnE;AACI,QAAM,SAAS,EAAE,GAAG,SAAS;AAE7B,YAAU,MAAM,kCAAkC;AAAA,IAC9C,cAAc,OAAO,KAAK,KAAK,EAAE;AAAA,IACjC;AAAA,EACJ,CAAC;AAED,aAAW,CAAC,SAAS,OAAO,KAAK,OAAO,QAAQ,KAAK,GACrD;AACI,QAAI,CAAC,WAAW,OAAO,YAAY,UACnC;AACI,gBAAU,MAAM,4CAA4C,EAAE,QAAQ,CAAC;AACvE;AAAA,IACJ;AAEA,UAAM,cAAc,OAAO,KAAK,OAAO;AACvC,cAAU,MAAM,sCAAsC;AAAA,MAClD;AAAA,MACA,YAAY,YAAY;AAAA,IAC5B,CAAC;AAED,eAAW,CAAC,SAAS,KAAK,KAAK,OAAO,QAAQ,OAAO,GACrD;AAEI,UAAI;AAEJ,UAAI,SAAS,OAAO,UAAU,YAAY,aAAa,OACvD;AACI,yBAAkB,MAAc;AAChC,kBAAU,MAAM,gDAAgD;AAAA,UAC5D;AAAA,UACA,YAAY;AAAA,QAChB,CAAC;AAAA,MACL,WACS,SAAS,OAAO,UAAU,YAAY,UAAU,OACzD;AACI,yBAAkB,MAAc,MAAM;AACtC,kBAAU,MAAM,+CAA+C;AAAA,UAC3D;AAAA,UACA;AAAA,QACJ,CAAC;AAAA,MACL,OAEA;AACI,yBAAiB;AACjB,kBAAU,MAAM,mCAAmC;AAAA,UAC/C;AAAA,UACA,WAAW,OAAO;AAAA,QACtB,CAAC;AAAA,MACL;AAGA,qBAAe,QAAQ,SAAS,cAAc;AAAA,IAClD;AAAA,EACJ;AAEA,YAAU,MAAM,mCAAmC;AAAA,IAC/C,YAAY,OAAO,KAAK,MAAM;AAAA,EAClC,CAAC;AAED,SAAO;AACX;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/lib/bind-locale.ts","../src/lib/helpers.ts","../src/lib/define-labels.ts"],"sourcesContent":["import { createApi } from \"@spfn/core/nextjs\";\nimport { logger } from \"@spfn/core/logger\";\nimport { type AppRouter } from './server/routes/index';\nimport { bindLocale, type SectionKeys, type BoundLabelSection, type BoundLabelsSections } from './lib/bind-locale';\nimport { setNestedValue } from './lib/helpers';\nimport { format, defineLabelConfig, defineLabels } from './lib/define-labels';\n\nconst cmsLogger = logger.child('@spfn/cms');\n\n/**\n * Default API client (for backward compatibility or when not using labels)\n */\nconst api = createApi<AppRouter>();\n\n/**\n * Create CMS client with API, label getters, and format utility\n *\n * @param labelsDefinition - Labels defined using defineLabels()\n * @param config - Label config from defineLabelConfig()\n * @returns API client, getLabel (single), getLabels (multiple), and format utility\n *\n * @example\n * ```typescript\n * // labels.ts - Setup once\n * import { getLocale } from '@spfn/cms/actions';\n *\n * export const { api, getLabel, getLabels, format } = createCmsClient(labelsDefinition, {\n * defaultLocale: 'ko',\n * getLocale: () => getLocale('ko'),\n * });\n *\n * // Single section - direct access\n * const label = await getLabel('home');\n * label.hero.title // \"Hello\" (no section name!)\n *\n * // Multiple sections - with section names\n * const labels = await getLabels(['home', 'about']);\n * labels.home.hero.title // \"Hello\"\n * labels.about.title // \"About Us\"\n *\n * // With template variables\n * const greeting = label.hero.greeting; // \"Hello {name}\"\n * format(greeting, { name: \"John\" }); // \"Hello John\"\n * ```\n */\nexport function createCmsClient<T>(\n labelsDefinition: T,\n config: {\n defaultLocale: string;\n fallbackLocale?: string;\n getLocale: () => Promise<string>;\n }\n)\n{\n /**\n * Get a single section's labels (without section name wrapper)\n *\n * @param section - Section name to fetch\n * @returns Labels for the section, directly accessible\n *\n * @example\n * ```typescript\n * const label = await getLabel('signup');\n * label.title // Direct access\n * label.userName\n * ```\n */\n async function getLabel<K extends SectionKeys<T>>(section: K): Promise<BoundLabelSection<T, K>>\n {\n // Auto-detect locale from cookie via injected getLocale\n const locale = await config.getLocale();\n\n cmsLogger.debug('getLabel called', {\n section,\n locale,\n defaultLocale: config.defaultLocale,\n fallbackLocale: config.fallbackLocale,\n });\n\n // 1. Fetch from published_cache\n const cache = await api.getLabelCache.call({\n query: {\n sections: [section as string],\n locale\n }\n });\n\n // 2. Filter only requested section\n const filteredLabels: any = {};\n if (section in (labelsDefinition as any))\n {\n filteredLabels[section] = (labelsDefinition as any)[section];\n }\n\n // 3. Generate defaults with locale binding\n const defaults = bindLocale(filteredLabels, locale, config.fallbackLocale);\n\n // 4. Merge: cache takes priority, fallback to defaults\n const merged = deepMergeCache(defaults, cache, locale);\n\n // 5. Return only the section content (without section name)\n return merged[section] as BoundLabelSection<T, K>;\n }\n\n /**\n * Get multiple sections' labels (with section names as keys)\n *\n * @param sections - Array of section names to fetch\n * @returns Object with section names as keys\n *\n * @example\n * ```typescript\n * const labels = await getLabels(['home', 'about']);\n * labels.home.title\n * labels.about.description\n * ```\n */\n async function getLabels<K extends SectionKeys<T>>(sections: readonly K[]): Promise<BoundLabelsSections<T, K>>\n {\n // Auto-detect locale from cookie via injected getLocale\n const locale = await config.getLocale();\n\n cmsLogger.debug('getLabels called', {\n sections,\n locale,\n defaultLocale: config.defaultLocale,\n fallbackLocale: config.fallbackLocale,\n availableDefinitionKeys: Object.keys(labelsDefinition as any),\n });\n\n // 1. Fetch from published_cache\n const cache = await api.getLabelCache.call({\n query: {\n sections: [...sections] as unknown as string[],\n locale\n }\n });\n\n cmsLogger.debug('Fetched from cache', {\n cacheKeys: Object.keys(cache),\n cacheEntryCount: Object.keys(cache).length,\n cacheStructure: Object.entries(cache).map(([key, value]) => ({\n section: key,\n isObject: typeof value === 'object',\n isNull: value === null,\n contentKeys: value && typeof value === 'object' ? Object.keys(value) : [],\n })),\n });\n\n // 2. Filter only requested sections (performance optimization)\n const filteredLabels: any = {};\n for (const section of sections)\n {\n if (section in (labelsDefinition as any))\n {\n filteredLabels[section] = (labelsDefinition as any)[section];\n }\n }\n\n cmsLogger.debug('Filtered sections', {\n requestedSections: sections,\n filteredSections: Object.keys(filteredLabels),\n filteredLabelsStructure: Object.entries(filteredLabels).map(([key, value]) => ({\n section: key,\n hasValue: !!value,\n isObject: typeof value === 'object',\n nestedKeys: value && typeof value === 'object' ? Object.keys(value) : [],\n })),\n });\n\n // 3. Generate defaults with locale binding (only for requested sections)\n const defaults = bindLocale(filteredLabels, locale, config.fallbackLocale);\n\n cmsLogger.debug('Generated defaults with locale binding', {\n defaultsKeys: Object.keys(defaults),\n });\n\n // 4. Merge: cache takes priority, fallback to defaults\n const merged = deepMergeCache(defaults, cache, locale);\n\n cmsLogger.debug('Merged cache and defaults', {\n mergedKeys: Object.keys(merged),\n });\n\n return merged as BoundLabelsSections<T, K>;\n }\n\n return { api, getLabel, getLabels, format };\n}\n\n/**\n * Deep merge cache into defaults\n */\nfunction deepMergeCache(defaults: any, cache: Record<string, any>, locale: string): any\n{\n const result = { ...defaults };\n\n cmsLogger.debug('deepMergeCache: Starting merge', {\n cacheEntries: Object.keys(cache).length,\n locale,\n });\n\n for (const [section, content] of Object.entries(cache))\n {\n if (!content || typeof content !== 'object')\n {\n cmsLogger.debug('deepMergeCache: Skipping invalid content', { section });\n continue;\n }\n\n const contentKeys = Object.keys(content);\n cmsLogger.debug('deepMergeCache: Processing section', {\n section,\n labelCount: contentKeys.length,\n });\n\n for (const [flatKey, value] of Object.entries(content))\n {\n // Extract locale-specific value from LabelValue format\n let extractedValue: any;\n\n if (value && typeof value === 'object' && 'content' in value)\n {\n extractedValue = (value as any).content;\n cmsLogger.debug('deepMergeCache: Extracted from content field', {\n flatKey,\n hasContent: true,\n });\n }\n else if (value && typeof value === 'object' && locale in value)\n {\n extractedValue = (value as any)[locale];\n cmsLogger.debug('deepMergeCache: Extracted from locale field', {\n flatKey,\n locale,\n });\n }\n else\n {\n extractedValue = value;\n cmsLogger.debug('deepMergeCache: Using raw value', {\n flatKey,\n valueType: typeof value,\n });\n }\n\n // Set value using helper function\n setNestedValue(result, flatKey, extractedValue);\n }\n }\n\n cmsLogger.debug('deepMergeCache: Merge completed', {\n resultKeys: Object.keys(result),\n });\n\n return result;\n}\n\n/**\n * Re-export format utility for standalone use\n *\n * @example\n * ```typescript\n * import { format } from '@spfn/cms/api-client';\n *\n * const text = \"Hello {name}, you have {count} messages\";\n * format(text, { name: \"John\", count: 5 });\n * // \"Hello John, you have 5 messages\"\n * ```\n */\nexport { format, defineLabelConfig, defineLabels };\n\n/**\n * Re-export types for external use\n */\nexport type { BoundLabels, SectionKeys, BoundLabelSection, BoundLabelsSections } from './lib/bind-locale';","/**\n * Bind locale to labels, returning locale-specific values\n *\n * @example\n * ```ts\n * const labelsDefinition = defineLabels({\n * home: {\n * title: { en: \"Home\", ko: \"홈\" }\n * }\n * });\n *\n * const labels = bindLocale(labelsDefinition, 'ko');\n * labels.home.title // \"홈\"\n * ```\n */\n\n/**\n * Type that converts locale records to strings\n */\nexport type BoundLabels<T> = {\n [K in keyof T]: T[K] extends Record<string, any>\n ? IsLocaleRecord<T[K]> extends true\n ? string\n : BoundLabels<T[K]>\n : T[K];\n};\n\n/**\n * Extract section keys from label definition\n */\nexport type SectionKeys<T> = Extract<keyof T, string>;\n\n/**\n * Get content of a single section (without section name wrapper)\n */\nexport type BoundLabelSection<T, K extends SectionKeys<T>> = BoundLabels<T>[K];\n\n/**\n * Pick specific sections from bound labels (for multiple sections)\n */\nexport type BoundLabelsSections<T, K extends SectionKeys<T>> = Pick<BoundLabels<T>, K>;\n\n/**\n * Check if object is a locale record (has string values only)\n */\ntype IsLocaleRecord<T> = T extends Record<string, string> ? true : false;\n\n/**\n * Check if an object is a locale record at runtime\n */\nfunction isLocaleRecord(obj: any): boolean\n{\n if (!obj || typeof obj !== 'object')\n {\n return false;\n }\n\n const values = Object.values(obj);\n\n // Empty object is not a locale record\n if (values.length === 0)\n {\n return false;\n }\n\n // All values must be strings\n return values.every(v => typeof v === 'string');\n}\n\n/**\n * Bind a locale to label definitions, returning locale-specific values\n *\n * @param labels - Label definitions with locale records\n * @param locale - Locale to bind (e.g., 'en', 'ko')\n * @param fallbackLocale - Optional fallback locale if value not found\n * @returns Labels with locale-specific string values\n *\n * @example\n * ```typescript\n * const labelsDefinition = defineLabels({\n * home: {\n * title: { en: \"Home\", ko: \"홈\" },\n * hero: {\n * title: { en: \"Welcome\", ko: \"환영합니다\" }\n * }\n * }\n * });\n *\n * const labels = bindLocale(labelsDefinition, 'ko');\n * labels.home.title // \"홈\"\n * labels.home.hero.title // \"환영합니다\"\n *\n * // With fallback\n * const labelsEn = bindLocale(labelsDefinition, 'en', 'ko');\n * ```\n */\nexport function bindLocale<T>(\n labels: T,\n locale: string,\n fallbackLocale?: string\n): BoundLabels<T>\n{\n return createProxy(labels, locale, fallbackLocale) as BoundLabels<T>;\n}\n\n/**\n * Create a proxy that intercepts property access and returns locale-specific values\n */\nfunction createProxy(obj: any, locale: string, fallbackLocale?: string): any\n{\n return new Proxy(obj, {\n get(target, prop)\n {\n const value = target[prop];\n\n // If value doesn't exist, return undefined\n if (value === undefined)\n {\n return undefined;\n }\n\n // If this is a locale record, return the locale value\n if (isLocaleRecord(value))\n {\n // Try to get the requested locale\n if (value[locale] !== undefined)\n {\n return value[locale];\n }\n\n // Fallback to fallbackLocale if specified\n if (fallbackLocale && value[fallbackLocale] !== undefined)\n {\n return value[fallbackLocale];\n }\n\n // If locale not found, return first available locale\n const firstLocale = Object.keys(value)[0];\n return value[firstLocale];\n }\n\n // If this is a nested object, wrap it in a proxy\n if (typeof value === 'object' && value !== null)\n {\n return createProxy(value, locale, fallbackLocale);\n }\n\n // Otherwise return as-is\n return value;\n },\n });\n}","/**\n * CMS Helper Functions\n */\n\nexport type FlatLabel = Record<string, Record<string, string>>;\n\n/**\n * Flatten nested label structure into dot notation\n *\n * @param labels - Nested label object\n * @param prefix - Key prefix for recursion\n * @returns Flattened label structure\n *\n * @example\n * ```typescript\n * const nested = {\n * home: {\n * hero: {\n * title: { en: \"Welcome\", ko: \"환영합니다\" }\n * }\n * }\n * };\n *\n * const flat = flattenLabels(nested);\n * // { \"home.hero.title\": { en: \"Welcome\", ko: \"환영합니다\" } }\n * ```\n */\nexport function flattenLabels<T extends Record<string, any>>(labels: T, prefix = ''): FlatLabel\n{\n const result: FlatLabel = {};\n\n if (!labels || typeof labels !== 'object')\n {\n return result;\n }\n\n const obj = labels as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(obj))\n {\n const newKey = prefix ? `${prefix}.${key}` : key;\n\n if (!value || typeof value !== 'object')\n {\n continue;\n }\n\n const valueObj = value as Record<string, unknown>;\n\n // Check if this is a leaf node (locale values: { en: \"...\", ko: \"...\" })\n const isLeaf = Object.values(valueObj).every(v => typeof v === 'string');\n\n if (isLeaf)\n {\n result[newKey] = valueObj as Record<string, string>;\n }\n else\n {\n // Recursively flatten nested structure\n Object.assign(result, flattenLabels(value, newKey));\n }\n }\n\n return result;\n}\n\n/**\n * Set a value in nested object using dot notation path\n *\n * @param target - Target object to modify\n * @param path - Dot notation path (e.g., \"home.hero.title\")\n * @param value - Value to set\n *\n * @example\n * ```typescript\n * const obj = {};\n * setNestedValue(obj, \"home.hero.title\", \"Welcome\");\n * // obj = { home: { hero: { title: \"Welcome\" } } }\n * ```\n */\nexport function setNestedValue(target: any, path: string, value: any): void\n{\n const parts = path.split('.');\n let current = target;\n\n for (let i = 0; i < parts.length - 1; i++)\n {\n const part = parts[i];\n if (!current[part])\n {\n current[part] = {};\n }\n current = current[part];\n }\n\n // Set the leaf value\n const lastPart = parts[parts.length - 1];\n current[lastPart] = value;\n}\n\n/**\n * Unflatten dot notation keys back to nested structure\n *\n * @param flat - Flattened label object\n * @returns Nested label structure\n *\n * @example\n * ```typescript\n * const flat = {\n * \"home.hero.title\": { en: \"Welcome\", ko: \"환영합니다\" },\n * \"home.hero.subtitle\": { en: \"Subtitle\", ko: \"부제목\" }\n * };\n *\n * const nested = unflattenLabels(flat);\n * // {\n * // home: {\n * // hero: {\n * // title: { en: \"Welcome\", ko: \"환영합니다\" },\n * // subtitle: { en: \"Subtitle\", ko: \"부제목\" }\n * // }\n * // }\n * // }\n * ```\n */\nexport function unflattenLabels(flat: FlatLabel): Record<string, any>\n{\n const result: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(flat))\n {\n setNestedValue(result, key, value);\n }\n\n return result;\n}","/**\n * Defines a type-safe label configuration.\n *\n * @example\n * ```ts\n * export const labelConfig = defineLabelConfig({\n * locales: ['en', 'ar'] as const,\n * defaultLocale: 'en',\n * fallbackLocale: 'en', // Optional\n * });\n *\n * export type LabelConfig = typeof labelConfig;\n * export type AppLocale = typeof labelConfig.locales[number]; // 'en' | 'ar'\n * ```\n */\nexport function defineLabelConfig<const TLocales extends readonly string[]>(config: {\n locales: TLocales;\n defaultLocale: TLocales[number];\n fallbackLocale?: TLocales[number];\n useBrowserLanguage?: boolean;\n})\n{\n return config;\n}\n\n/**\n * Define nested label structure (tRPC-style)\n *\n * @example\n * ```ts\n * export const labels = defineLabels({\n * home: {\n * slogan: { en: \"Welcome\", ko: \"환영합니다\" },\n * hero: {\n * title: { en: \"Hello\", ko: \"안녕하세요\" }\n * }\n * },\n * about: {\n * title: { en: \"About Us\", ko: \"회사 소개\" }\n * }\n * });\n *\n * // Usage\n * labels.home.slogan;\n * labels.home.hero.title;\n * labels.about.title;\n * ```\n */\nexport function defineLabels<const T>(labels: T)\n{\n return labels;\n}\n\nexport function format(template: string, vars: Record<string, string | number>): string\n{\n return template.replace(/\\{(\\w+)}/g, (match, key) =>\n {\n const value = vars[key];\n return value !== undefined ? String(value) : match;\n });\n}"],"mappings":"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","names":[]}
|