@hua-labs/i18n-loaders 2.0.5 → 2.2.0
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 +2 -1
- package/dist/{index.js → index.cjs} +2 -2
- package/dist/index.cjs.map +1 -0
- package/package.json +11 -9
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Production-ready translation loaders with built-in TTL caching, duplicate reques
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@hua-labs/i18n-loaders)
|
|
6
6
|
[](https://www.npmjs.com/package/@hua-labs/i18n-loaders)
|
|
7
|
-
[](https://github.com/HUA-Labs/
|
|
7
|
+
[](https://github.com/HUA-Labs/hua-packages/blob/main/LICENSE)
|
|
8
8
|
[](https://www.typescriptlang.org/)
|
|
9
9
|
[](https://reactjs.org/)
|
|
10
10
|
|
|
@@ -14,6 +14,7 @@ Production-ready translation loaders with built-in TTL caching, duplicate reques
|
|
|
14
14
|
- **TTL caching — Time-based cache with global cache support**
|
|
15
15
|
- **Duplicate prevention — Deduplicates concurrent requests for the same resource**
|
|
16
16
|
- **Preloading — Warm up namespaces and fallback languages at startup**
|
|
17
|
+
- **React Native — Works in Expo and bare RN projects**
|
|
17
18
|
- **Default merging — Merge API translations with bundled defaults**
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
@@ -268,5 +268,5 @@ exports.createApiTranslationLoader = createApiTranslationLoader;
|
|
|
268
268
|
exports.preloadNamespaces = preloadNamespaces;
|
|
269
269
|
exports.warmFallbackLanguages = warmFallbackLanguages;
|
|
270
270
|
exports.withDefaultTranslations = withDefaultTranslations;
|
|
271
|
-
//# sourceMappingURL=index.
|
|
272
|
-
//# sourceMappingURL=index.
|
|
271
|
+
//# sourceMappingURL=index.cjs.map
|
|
272
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api-loader.ts","../src/preload.ts","../src/defaults.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import { ApiLoaderOptions, TranslationLoader, TranslationRecord } from './types';\n\ninterface CacheEntry {\n data: TranslationRecord;\n expiresAt: number;\n}\n\nconst FIVE_MINUTES = 5 * 60 * 1000;\n\nconst defaultFetcher = (input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, init);\n\n/**\n * 재시도 가능한 에러인지 확인\n */\nfunction isRetryableError(error: unknown): boolean {\n // 네트워크 에러 (TypeError)\n if (error instanceof TypeError) {\n return true;\n }\n\n // Fetch API 에러 메시지 확인\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (\n message.includes('failed to fetch') ||\n message.includes('networkerror') ||\n message.includes('network request failed')\n ) {\n return true;\n }\n }\n\n // Response 객체가 있는 경우 HTTP 상태 코드 확인\n if (error && typeof error === 'object' && 'status' in error) {\n const status = (error as { status: number }).status;\n // 5xx 서버 에러는 재시도 가능\n if (status >= 500 && status < 600) {\n return true;\n }\n // 408 Request Timeout도 재시도 가능\n if (status === 408) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * API 기반 번역 로더 생성\n * \n * @param options - 로더 옵션\n * @returns 번역 로더 및 캐시 무효화 함수\n * \n * @example\n * ```typescript\n * const { loader, invalidate, clear } = createApiTranslationLoader({\n * translationApiPath: '/api/translations',\n * autoInvalidateInDev: true\n * });\n * \n * // 특정 언어/네임스페이스 무효화\n * invalidate('ko', 'common');\n * \n * // 전체 캐시 클리어\n * clear();\n * ```\n */\nexport function createApiTranslationLoader(\n options: ApiLoaderOptions = {}\n): TranslationLoader & { invalidate: (language?: string, namespace?: string) => void; clear: () => void } {\n const translationApiPath = options.translationApiPath ?? '/api/translations';\n const cacheTtlMs = options.cacheTtlMs ?? FIVE_MINUTES;\n const fetcher = options.fetcher ?? defaultFetcher;\n const logger = options.logger ?? console;\n const retryCount = options.retryCount ?? 0;\n const retryDelay = options.retryDelay ?? 1000;\n const autoInvalidateInDev = options.autoInvalidateInDev ?? (typeof process !== 'undefined' && process.env.NODE_ENV === 'development');\n const localCache = new Map<string, CacheEntry>();\n const inFlightRequests = new Map<string, Promise<TranslationRecord>>();\n\n const buildUrl = (language: string, namespace: string) => {\n const safeNamespace = namespace.replace(/[^a-zA-Z0-9-_]/g, '');\n const path = `${translationApiPath}/${language}/${safeNamespace}`;\n\n if (typeof window !== 'undefined') {\n return path;\n }\n\n if (options.baseUrl) {\n return `${options.baseUrl}${path}`;\n }\n\n if (process.env.NEXT_PUBLIC_SITE_URL) {\n return `${process.env.NEXT_PUBLIC_SITE_URL}${path}`;\n }\n\n if (process.env.VERCEL_URL) {\n const vercelUrl = process.env.VERCEL_URL.startsWith('http')\n ? process.env.VERCEL_URL\n : `https://${process.env.VERCEL_URL}`;\n return `${vercelUrl}${path}`;\n }\n\n const fallbackBase = options.localFallbackBaseUrl ?? 'http://localhost:3000';\n return `${fallbackBase}${path}`;\n };\n\n const getRequestInit = (language: string, namespace: string): RequestInit => {\n if (typeof options.requestInit === 'function') {\n return options.requestInit(language, namespace) ?? {};\n }\n\n return options.requestInit ?? {};\n };\n\n const getCached = (cacheKey: string) => {\n if (options.disableCache) {\n return null;\n }\n\n const entry = localCache.get(cacheKey);\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt < Date.now()) {\n localCache.delete(cacheKey);\n return null;\n }\n\n return entry.data;\n };\n\n const setCached = (cacheKey: string, data: TranslationRecord) => {\n if (options.disableCache) {\n return;\n }\n\n localCache.set(cacheKey, {\n data,\n expiresAt: Date.now() + cacheTtlMs\n });\n };\n\n const loadTranslations: TranslationLoader = async (language, namespace) => {\n const cacheKey = `${language}:${namespace}`;\n const cached = getCached(cacheKey);\n\n if (cached) {\n return cached;\n }\n\n const inFlight = inFlightRequests.get(cacheKey);\n if (inFlight) {\n return inFlight;\n }\n\n const url = buildUrl(language, namespace);\n const requestInit = getRequestInit(language, namespace);\n\n const performRequest = async (attempt: number): Promise<TranslationRecord> => {\n try {\n const response = await fetcher(url, {\n cache: 'no-store',\n ...requestInit\n });\n\n if (!response.ok) {\n throw new Error(\n `[i18n-loaders] Failed to load ${language}/${namespace} (${response.status})`\n );\n }\n\n const data = (await response.json()) as TranslationRecord;\n setCached(cacheKey, data);\n return data;\n } catch (error) {\n // 재시도 가능한 에러인지 확인\n const isRetryable = isRetryableError(error);\n\n if (isRetryable && attempt < retryCount) {\n logger.warn?.(\n `[i18n-loaders] Translation fetch failed (attempt ${attempt + 1}/${retryCount + 1}), retrying...`,\n language,\n namespace,\n error\n );\n \n // 지수 백오프: 각 재시도마다 지연 시간 증가\n const delay = retryDelay * Math.pow(2, attempt);\n await new Promise(resolve => setTimeout(resolve, delay));\n \n return performRequest(attempt + 1);\n }\n\n // 재시도 불가능하거나 재시도 횟수 초과\n logger.warn?.(\n '[i18n-loaders] translation fetch failed',\n language,\n namespace,\n error\n );\n throw error;\n }\n };\n\n const requestPromise = performRequest(0)\n .finally(() => {\n inFlightRequests.delete(cacheKey);\n });\n\n inFlightRequests.set(cacheKey, requestPromise);\n return requestPromise;\n };\n\n /**\n * 특정 언어/네임스페이스의 캐시 무효화\n */\n const invalidate = (language?: string, namespace?: string) => {\n if (language && namespace) {\n // 특정 언어/네임스페이스만 무효화\n const cacheKey = `${language}:${namespace}`;\n localCache.delete(cacheKey);\n logger.log?.('[i18n-loaders] Cache invalidated:', cacheKey);\n } else if (language) {\n // 특정 언어의 모든 네임스페이스 무효화\n const prefix = `${language}:`;\n for (const key of localCache.keys()) {\n if (key.startsWith(prefix)) {\n localCache.delete(key);\n }\n }\n logger.log?.('[i18n-loaders] Cache invalidated for language:', language);\n } else if (namespace) {\n // 특정 네임스페이스의 모든 언어 무효화\n const suffix = `:${namespace}`;\n for (const key of localCache.keys()) {\n if (key.endsWith(suffix)) {\n localCache.delete(key);\n }\n }\n logger.log?.('[i18n-loaders] Cache invalidated for namespace:', namespace);\n } else {\n // 전체 캐시 무효화\n clear();\n }\n };\n\n /**\n * 전체 캐시 클리어\n */\n const clear = () => {\n localCache.clear();\n logger.log?.('[i18n-loaders] Cache cleared');\n };\n\n // 개발 모드에서 자동 무효화 설정\n if (autoInvalidateInDev && typeof window !== 'undefined') {\n // 개발 모드에서 페이지 포커스 시 캐시 무효화 (번역 파일 변경 감지)\n window.addEventListener('focus', () => {\n if (process.env.NODE_ENV === 'development') {\n logger.log?.('[i18n-loaders] Development mode: Auto-invalidating cache on focus');\n clear();\n }\n });\n }\n\n // loadTranslations 함수에 invalidate와 clear 메서드 추가\n const loader = loadTranslations as TranslationLoader & {\n invalidate: (language?: string, namespace?: string) => void;\n clear: () => void;\n };\n \n loader.invalidate = invalidate;\n loader.clear = clear;\n\n return loader;\n}\n\n","import { PreloadOptions, TranslationLoader } from './types';\n\nconst defaultLogger = console;\n\nexport async function preloadNamespaces(\n language: string,\n namespaces: string[],\n loader: TranslationLoader,\n options: PreloadOptions = {}\n) {\n const logger = options.logger ?? defaultLogger;\n\n const results = await Promise.allSettled(\n namespaces.map(async (namespace) => {\n await loader(language, namespace);\n return namespace;\n })\n );\n\n const fulfilled = results.filter(\n (result): result is PromiseFulfilledResult<string> =>\n result.status === 'fulfilled'\n );\n const rejected = results.filter(\n (result): result is PromiseRejectedResult => result.status === 'rejected'\n );\n\n if (fulfilled.length > 0) {\n logger.log?.(\n `[i18n-loaders] Preloaded ${fulfilled.length}/${namespaces.length} namespaces for ${language}`\n );\n }\n\n if (rejected.length > 0 && !options.suppressErrors) {\n logger.warn?.(\n `[i18n-loaders] Failed to preload ${rejected.length} namespaces for ${language}`\n );\n }\n\n return {\n fulfilled: fulfilled.map((result) => result.value),\n rejected: rejected.map((result) => result.reason)\n };\n}\n\nexport async function warmFallbackLanguages(\n currentLanguage: string,\n languages: string[],\n namespaces: string[],\n loader: TranslationLoader,\n options: PreloadOptions = {}\n) {\n const targets = languages.filter((language) => language !== currentLanguage);\n if (targets.length === 0) {\n return [];\n }\n\n return Promise.all(\n targets.map((language) =>\n preloadNamespaces(language, namespaces, loader, options)\n )\n );\n}\n\n","import {\n DefaultTranslations,\n TranslationLoader,\n TranslationRecord\n} from './types';\n\nexport function withDefaultTranslations(\n loader: TranslationLoader,\n defaults: DefaultTranslations\n): TranslationLoader {\n return async (language, namespace) => {\n const fallback = defaults[language]?.[namespace];\n\n try {\n const remote = await loader(language, namespace);\n \n // API 응답이 빈 객체이거나 null/undefined인 경우 fallback 반환\n if (!remote || (typeof remote === 'object' && Object.keys(remote).length === 0)) {\n if (fallback) {\n return fallback;\n }\n return remote || {};\n }\n \n if (!fallback) {\n return remote;\n }\n\n return mergeTranslations(fallback, remote);\n } catch (error) {\n if (fallback) {\n return fallback;\n }\n throw error;\n }\n };\n}\n\nfunction mergeTranslations(\n base: TranslationRecord,\n override: TranslationRecord\n): TranslationRecord {\n const result: TranslationRecord = { ...base };\n\n for (const [key, value] of Object.entries(override)) {\n if (isPlainObject(value) && isPlainObject(result[key])) {\n result[key] = mergeTranslations(\n result[key] as TranslationRecord,\n value as TranslationRecord\n );\n continue;\n }\n\n result[key] = value;\n }\n\n return result;\n}\n\nfunction isPlainObject(value: unknown): value is TranslationRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n"]}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hua-labs/i18n-loaders",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Production-ready loaders, caching, and preloading helpers for @hua-labs/i18n-core.",
|
|
5
|
-
"main": "./dist/index.
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"files": [
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
],
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
+
"react-native": "./dist/index.mjs",
|
|
14
15
|
"types": "./dist/index.d.ts",
|
|
15
16
|
"import": "./dist/index.mjs",
|
|
16
|
-
"require": "./dist/index.
|
|
17
|
+
"require": "./dist/index.cjs",
|
|
18
|
+
"default": "./dist/index.mjs"
|
|
17
19
|
}
|
|
18
20
|
},
|
|
19
21
|
"sideEffects": false,
|
|
@@ -21,15 +23,15 @@
|
|
|
21
23
|
"node": ">=20.0.0"
|
|
22
24
|
},
|
|
23
25
|
"dependencies": {
|
|
24
|
-
"@hua-labs/i18n-core": "
|
|
26
|
+
"@hua-labs/i18n-core": "2.2.0"
|
|
25
27
|
},
|
|
26
28
|
"peerDependencies": {
|
|
27
29
|
"react": ">=19.0.0"
|
|
28
30
|
},
|
|
29
31
|
"devDependencies": {
|
|
30
|
-
"@types/node": "^25.
|
|
32
|
+
"@types/node": "^25.3.5",
|
|
31
33
|
"@types/react": "^19.2.14",
|
|
32
|
-
"eslint": "^10.0.
|
|
34
|
+
"eslint": "^10.0.3",
|
|
33
35
|
"react": "^19.2.4",
|
|
34
36
|
"tsup": "^8.5.1",
|
|
35
37
|
"typescript": "^5.9.3",
|
|
@@ -50,12 +52,12 @@
|
|
|
50
52
|
"license": "MIT",
|
|
51
53
|
"repository": {
|
|
52
54
|
"type": "git",
|
|
53
|
-
"url": "https://github.com/HUA-Labs/
|
|
55
|
+
"url": "https://github.com/HUA-Labs/hua-packages.git"
|
|
54
56
|
},
|
|
55
57
|
"bugs": {
|
|
56
|
-
"url": "https://github.com/HUA-Labs/
|
|
58
|
+
"url": "https://github.com/HUA-Labs/hua-packages/issues"
|
|
57
59
|
},
|
|
58
|
-
"homepage": "https://github.com/HUA-Labs/
|
|
60
|
+
"homepage": "https://github.com/HUA-Labs/hua-packages#readme",
|
|
59
61
|
"publishConfig": {
|
|
60
62
|
"access": "public",
|
|
61
63
|
"provenance": true
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api-loader.ts","../src/preload.ts","../src/defaults.ts"],"names":[],"mappings":";;;AAOA,IAAM,YAAA,GAAe,IAAI,EAAA,GAAK,GAAA;AAE9B,IAAM,iBAAiB,CAAC,KAAA,EAA0B,IAAA,KAChD,KAAA,CAAM,OAAO,IAAI,CAAA;AAKnB,SAAS,iBAAiB,KAAA,EAAyB;AAEjD,EAAA,IAAI,iBAAiB,SAAA,EAAW;AAC9B,IAAA,OAAO,IAAA;AAAA,EACT;AAGA,EAAA,IAAI,iBAAiB,KAAA,EAAO;AAC1B,IAAA,MAAM,OAAA,GAAU,KAAA,CAAM,OAAA,CAAQ,WAAA,EAAY;AAC1C,IAAA,IACE,OAAA,CAAQ,QAAA,CAAS,iBAAiB,CAAA,IAClC,OAAA,CAAQ,QAAA,CAAS,cAAc,CAAA,IAC/B,OAAA,CAAQ,QAAA,CAAS,wBAAwB,CAAA,EACzC;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAGA,EAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,IAAY,YAAY,KAAA,EAAO;AAC3D,IAAA,MAAM,SAAU,KAAA,CAA6B,MAAA;AAE7C,IAAA,IAAI,MAAA,IAAU,GAAA,IAAO,MAAA,GAAS,GAAA,EAAK;AACjC,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,IAAI,WAAW,GAAA,EAAK;AAClB,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,KAAA;AACT;AAsBO,SAAS,0BAAA,CACd,OAAA,GAA4B,EAAC,EAC2E;AACxG,EAAA,MAAM,kBAAA,GAAqB,QAAQ,kBAAA,IAAsB,mBAAA;AACzD,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,YAAA;AACzC,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,IAAW,cAAA;AACnC,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,OAAA;AACjC,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,CAAA;AACzC,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,GAAA;AACzC,EAAA,MAAM,mBAAA,GAAsB,QAAQ,mBAAA,KAAwB,OAAO,YAAY,WAAA,IAAe,OAAA,CAAQ,IAAI,QAAA,KAAa,aAAA,CAAA;AACvH,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAAwB;AAC/C,EAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAwC;AAErE,EAAA,MAAM,QAAA,GAAW,CAAC,QAAA,EAAkB,SAAA,KAAsB;AACxD,IAAA,MAAM,aAAA,GAAgB,SAAA,CAAU,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAA;AAC7D,IAAA,MAAM,OAAO,CAAA,EAAG,kBAAkB,CAAA,CAAA,EAAI,QAAQ,IAAI,aAAa,CAAA,CAAA;AAE/D,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,MAAA,OAAO,CAAA,EAAG,OAAA,CAAQ,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA;AAAA,IAClC;AAEA,IAAA,IAAI,OAAA,CAAQ,IAAI,oBAAA,EAAsB;AACpC,MAAA,OAAO,CAAA,EAAG,OAAA,CAAQ,GAAA,CAAI,oBAAoB,GAAG,IAAI,CAAA,CAAA;AAAA,IACnD;AAEA,IAAA,IAAI,OAAA,CAAQ,IAAI,UAAA,EAAY;AAC1B,MAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,UAAA,CAAW,UAAA,CAAW,MAAM,CAAA,GACtD,OAAA,CAAQ,GAAA,CAAI,UAAA,GACZ,CAAA,QAAA,EAAW,OAAA,CAAQ,IAAI,UAAU,CAAA,CAAA;AACrC,MAAA,OAAO,CAAA,EAAG,SAAS,CAAA,EAAG,IAAI,CAAA,CAAA;AAAA,IAC5B;AAEA,IAAA,MAAM,YAAA,GAAe,QAAQ,oBAAA,IAAwB,uBAAA;AACrD,IAAA,OAAO,CAAA,EAAG,YAAY,CAAA,EAAG,IAAI,CAAA,CAAA;AAAA,EAC/B,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB,CAAC,QAAA,EAAkB,SAAA,KAAmC;AAC3E,IAAA,IAAI,OAAO,OAAA,CAAQ,WAAA,KAAgB,UAAA,EAAY;AAC7C,MAAA,OAAO,OAAA,CAAQ,WAAA,CAAY,QAAA,EAAU,SAAS,KAAK,EAAC;AAAA,IACtD;AAEA,IAAA,OAAO,OAAA,CAAQ,eAAe,EAAC;AAAA,EACjC,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,QAAA,KAAqB;AACtC,IAAA,IAAI,QAAQ,YAAA,EAAc;AACxB,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,GAAA,CAAI,QAAQ,CAAA;AACrC,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,IAAI,KAAA,CAAM,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI,EAAG;AAChC,MAAA,UAAA,CAAW,OAAO,QAAQ,CAAA;AAC1B,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,OAAO,KAAA,CAAM,IAAA;AAAA,EACf,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,QAAA,EAAkB,IAAA,KAA4B;AAC/D,IAAA,IAAI,QAAQ,YAAA,EAAc;AACxB,MAAA;AAAA,IACF;AAEA,IAAA,UAAA,CAAW,IAAI,QAAA,EAAU;AAAA,MACvB,IAAA;AAAA,MACA,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,GAAI;AAAA,KACzB,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,gBAAA,GAAsC,OAAO,QAAA,EAAU,SAAA,KAAc;AACzE,IAAA,MAAM,QAAA,GAAW,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA;AACzC,IAAA,MAAM,MAAA,GAAS,UAAU,QAAQ,CAAA;AAEjC,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,MAAM,QAAA,GAAW,gBAAA,CAAiB,GAAA,CAAI,QAAQ,CAAA;AAC9C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,OAAO,QAAA;AAAA,IACT;AAEA,IAAA,MAAM,GAAA,GAAM,QAAA,CAAS,QAAA,EAAU,SAAS,CAAA;AACxC,IAAA,MAAM,WAAA,GAAc,cAAA,CAAe,QAAA,EAAU,SAAS,CAAA;AAEtD,IAAA,MAAM,cAAA,GAAiB,OAAO,OAAA,KAAgD;AAC5E,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,GAAA,EAAK;AAAA,UAClC,KAAA,EAAO,UAAA;AAAA,UACP,GAAG;AAAA,SACJ,CAAA;AAED,QAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,iCAAiC,QAAQ,CAAA,CAAA,EAAI,SAAS,CAAA,EAAA,EAAK,SAAS,MAAM,CAAA,CAAA;AAAA,WAC5E;AAAA,QACF;AAEA,QAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAClC,QAAA,SAAA,CAAU,UAAU,IAAI,CAAA;AACxB,QAAA,OAAO,IAAA;AAAA,MACT,SAAS,KAAA,EAAO;AAEd,QAAA,MAAM,WAAA,GAAc,iBAAiB,KAAK,CAAA;AAE1C,QAAA,IAAI,WAAA,IAAe,UAAU,UAAA,EAAY;AACvC,UAAA,MAAA,CAAO,IAAA;AAAA,YACL,CAAA,iDAAA,EAAoD,OAAA,GAAU,CAAC,CAAA,CAAA,EAAI,aAAa,CAAC,CAAA,cAAA,CAAA;AAAA,YACjF,QAAA;AAAA,YACA,SAAA;AAAA,YACA;AAAA,WACF;AAGA,UAAA,MAAM,KAAA,GAAQ,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,GAAG,OAAO,CAAA;AAC9C,UAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,KAAK,CAAC,CAAA;AAEvD,UAAA,OAAO,cAAA,CAAe,UAAU,CAAC,CAAA;AAAA,QACnC;AAGA,QAAA,MAAA,CAAO,IAAA;AAAA,UACL,yCAAA;AAAA,UACA,QAAA;AAAA,UACA,SAAA;AAAA,UACA;AAAA,SACF;AACA,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,cAAA,GAAiB,cAAA,CAAe,CAAC,CAAA,CACpC,QAAQ,MAAM;AACb,MAAA,gBAAA,CAAiB,OAAO,QAAQ,CAAA;AAAA,IAClC,CAAC,CAAA;AAEH,IAAA,gBAAA,CAAiB,GAAA,CAAI,UAAU,cAAc,CAAA;AAC7C,IAAA,OAAO,cAAA;AAAA,EACT,CAAA;AAKA,EAAA,MAAM,UAAA,GAAa,CAAC,QAAA,EAAmB,SAAA,KAAuB;AAC5D,IAAA,IAAI,YAAY,SAAA,EAAW;AAEzB,MAAA,MAAM,QAAA,GAAW,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA;AACzC,MAAA,UAAA,CAAW,OAAO,QAAQ,CAAA;AAC1B,MAAA,MAAA,CAAO,GAAA,GAAM,qCAAqC,QAAQ,CAAA;AAAA,IAC5D,WAAW,QAAA,EAAU;AAEnB,MAAA,MAAM,MAAA,GAAS,GAAG,QAAQ,CAAA,CAAA,CAAA;AAC1B,MAAA,KAAA,MAAW,GAAA,IAAO,UAAA,CAAW,IAAA,EAAK,EAAG;AACnC,QAAA,IAAI,GAAA,CAAI,UAAA,CAAW,MAAM,CAAA,EAAG;AAC1B,UAAA,UAAA,CAAW,OAAO,GAAG,CAAA;AAAA,QACvB;AAAA,MACF;AACA,MAAA,MAAA,CAAO,GAAA,GAAM,kDAAkD,QAAQ,CAAA;AAAA,IACzE,WAAW,SAAA,EAAW;AAEpB,MAAA,MAAM,MAAA,GAAS,IAAI,SAAS,CAAA,CAAA;AAC5B,MAAA,KAAA,MAAW,GAAA,IAAO,UAAA,CAAW,IAAA,EAAK,EAAG;AACnC,QAAA,IAAI,GAAA,CAAI,QAAA,CAAS,MAAM,CAAA,EAAG;AACxB,UAAA,UAAA,CAAW,OAAO,GAAG,CAAA;AAAA,QACvB;AAAA,MACF;AACA,MAAA,MAAA,CAAO,GAAA,GAAM,mDAAmD,SAAS,CAAA;AAAA,IAC3E,CAAA,MAAO;AAEL,MAAA,KAAA,EAAM;AAAA,IACR;AAAA,EACF,CAAA;AAKA,EAAA,MAAM,QAAQ,MAAM;AAClB,IAAA,UAAA,CAAW,KAAA,EAAM;AACjB,IAAA,MAAA,CAAO,MAAM,8BAA8B,CAAA;AAAA,EAC7C,CAAA;AAGA,EAAA,IAAI,mBAAA,IAAuB,OAAO,MAAA,KAAW,WAAA,EAAa;AAExD,IAAA,MAAA,CAAO,gBAAA,CAAiB,SAAS,MAAM;AACrC,MAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,aAAA,EAAe;AAC1C,QAAA,MAAA,CAAO,MAAM,mEAAmE,CAAA;AAChF,QAAA,KAAA,EAAM;AAAA,MACR;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AAGA,EAAA,MAAM,MAAA,GAAS,gBAAA;AAKf,EAAA,MAAA,CAAO,UAAA,GAAa,UAAA;AACpB,EAAA,MAAA,CAAO,KAAA,GAAQ,KAAA;AAEf,EAAA,OAAO,MAAA;AACT;;;ACrRA,IAAM,aAAA,GAAgB,OAAA;AAEtB,eAAsB,kBACpB,QAAA,EACA,UAAA,EACA,MAAA,EACA,OAAA,GAA0B,EAAC,EAC3B;AACA,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,aAAA;AAEjC,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,UAAA;AAAA,IAC5B,UAAA,CAAW,GAAA,CAAI,OAAO,SAAA,KAAc;AAClC,MAAA,MAAM,MAAA,CAAO,UAAU,SAAS,CAAA;AAChC,MAAA,OAAO,SAAA;AAAA,IACT,CAAC;AAAA,GACH;AAEA,EAAA,MAAM,YAAY,OAAA,CAAQ,MAAA;AAAA,IACxB,CAAC,MAAA,KACC,MAAA,CAAO,MAAA,KAAW;AAAA,GACtB;AACA,EAAA,MAAM,WAAW,OAAA,CAAQ,MAAA;AAAA,IACvB,CAAC,MAAA,KAA4C,MAAA,CAAO,MAAA,KAAW;AAAA,GACjE;AAEA,EAAA,IAAI,SAAA,CAAU,SAAS,CAAA,EAAG;AACxB,IAAA,MAAA,CAAO,GAAA;AAAA,MACL,4BAA4B,SAAA,CAAU,MAAM,IAAI,UAAA,CAAW,MAAM,mBAAmB,QAAQ,CAAA;AAAA,KAC9F;AAAA,EACF;AAEA,EAAA,IAAI,QAAA,CAAS,MAAA,GAAS,CAAA,IAAK,CAAC,QAAQ,cAAA,EAAgB;AAClD,IAAA,MAAA,CAAO,IAAA;AAAA,MACL,CAAA,iCAAA,EAAoC,QAAA,CAAS,MAAM,CAAA,gBAAA,EAAmB,QAAQ,CAAA;AAAA,KAChF;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,WAAW,SAAA,CAAU,GAAA,CAAI,CAAC,MAAA,KAAW,OAAO,KAAK,CAAA;AAAA,IACjD,UAAU,QAAA,CAAS,GAAA,CAAI,CAAC,MAAA,KAAW,OAAO,MAAM;AAAA,GAClD;AACF;AAEA,eAAsB,sBACpB,eAAA,EACA,SAAA,EACA,YACA,MAAA,EACA,OAAA,GAA0B,EAAC,EAC3B;AACA,EAAA,MAAM,UAAU,SAAA,CAAU,MAAA,CAAO,CAAC,QAAA,KAAa,aAAa,eAAe,CAAA;AAC3E,EAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AACxB,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,OAAO,OAAA,CAAQ,GAAA;AAAA,IACb,OAAA,CAAQ,GAAA;AAAA,MAAI,CAAC,QAAA,KACX,iBAAA,CAAkB,QAAA,EAAU,UAAA,EAAY,QAAQ,OAAO;AAAA;AACzD,GACF;AACF;;;ACxDO,SAAS,uBAAA,CACd,QACA,QAAA,EACmB;AACnB,EAAA,OAAO,OAAO,UAAU,SAAA,KAAc;AACpC,IAAA,MAAM,QAAA,GAAW,QAAA,CAAS,QAAQ,CAAA,GAAI,SAAS,CAAA;AAE/C,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,QAAA,EAAU,SAAS,CAAA;AAG/C,MAAA,IAAI,CAAC,MAAA,IAAW,OAAO,MAAA,KAAW,QAAA,IAAY,OAAO,IAAA,CAAK,MAAM,CAAA,CAAE,MAAA,KAAW,CAAA,EAAI;AAC/E,QAAA,IAAI,QAAA,EAAU;AACZ,UAAA,OAAO,QAAA;AAAA,QACT;AACA,QAAA,OAAO,UAAU,EAAC;AAAA,MACpB;AAEA,MAAA,IAAI,CAAC,QAAA,EAAU;AACb,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,OAAO,iBAAA,CAAkB,UAAU,MAAM,CAAA;AAAA,IAC3C,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,OAAO,QAAA;AAAA,MACT;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF,CAAA;AACF;AAEA,SAAS,iBAAA,CACP,MACA,QAAA,EACmB;AACnB,EAAA,MAAM,MAAA,GAA4B,EAAE,GAAG,IAAA,EAAK;AAE5C,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,EAAG;AACnD,IAAA,IAAI,cAAc,KAAK,CAAA,IAAK,cAAc,MAAA,CAAO,GAAG,CAAC,CAAA,EAAG;AACtD,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,iBAAA;AAAA,QACZ,OAAO,GAAG,CAAA;AAAA,QACV;AAAA,OACF;AACA,MAAA;AAAA,IACF;AAEA,IAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA;AAAA,EAChB;AAEA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,cAAc,KAAA,EAA4C;AACjE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E","file":"index.js","sourcesContent":["import { ApiLoaderOptions, TranslationLoader, TranslationRecord } from './types';\n\ninterface CacheEntry {\n data: TranslationRecord;\n expiresAt: number;\n}\n\nconst FIVE_MINUTES = 5 * 60 * 1000;\n\nconst defaultFetcher = (input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, init);\n\n/**\n * 재시도 가능한 에러인지 확인\n */\nfunction isRetryableError(error: unknown): boolean {\n // 네트워크 에러 (TypeError)\n if (error instanceof TypeError) {\n return true;\n }\n\n // Fetch API 에러 메시지 확인\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (\n message.includes('failed to fetch') ||\n message.includes('networkerror') ||\n message.includes('network request failed')\n ) {\n return true;\n }\n }\n\n // Response 객체가 있는 경우 HTTP 상태 코드 확인\n if (error && typeof error === 'object' && 'status' in error) {\n const status = (error as { status: number }).status;\n // 5xx 서버 에러는 재시도 가능\n if (status >= 500 && status < 600) {\n return true;\n }\n // 408 Request Timeout도 재시도 가능\n if (status === 408) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * API 기반 번역 로더 생성\n * \n * @param options - 로더 옵션\n * @returns 번역 로더 및 캐시 무효화 함수\n * \n * @example\n * ```typescript\n * const { loader, invalidate, clear } = createApiTranslationLoader({\n * translationApiPath: '/api/translations',\n * autoInvalidateInDev: true\n * });\n * \n * // 특정 언어/네임스페이스 무효화\n * invalidate('ko', 'common');\n * \n * // 전체 캐시 클리어\n * clear();\n * ```\n */\nexport function createApiTranslationLoader(\n options: ApiLoaderOptions = {}\n): TranslationLoader & { invalidate: (language?: string, namespace?: string) => void; clear: () => void } {\n const translationApiPath = options.translationApiPath ?? '/api/translations';\n const cacheTtlMs = options.cacheTtlMs ?? FIVE_MINUTES;\n const fetcher = options.fetcher ?? defaultFetcher;\n const logger = options.logger ?? console;\n const retryCount = options.retryCount ?? 0;\n const retryDelay = options.retryDelay ?? 1000;\n const autoInvalidateInDev = options.autoInvalidateInDev ?? (typeof process !== 'undefined' && process.env.NODE_ENV === 'development');\n const localCache = new Map<string, CacheEntry>();\n const inFlightRequests = new Map<string, Promise<TranslationRecord>>();\n\n const buildUrl = (language: string, namespace: string) => {\n const safeNamespace = namespace.replace(/[^a-zA-Z0-9-_]/g, '');\n const path = `${translationApiPath}/${language}/${safeNamespace}`;\n\n if (typeof window !== 'undefined') {\n return path;\n }\n\n if (options.baseUrl) {\n return `${options.baseUrl}${path}`;\n }\n\n if (process.env.NEXT_PUBLIC_SITE_URL) {\n return `${process.env.NEXT_PUBLIC_SITE_URL}${path}`;\n }\n\n if (process.env.VERCEL_URL) {\n const vercelUrl = process.env.VERCEL_URL.startsWith('http')\n ? process.env.VERCEL_URL\n : `https://${process.env.VERCEL_URL}`;\n return `${vercelUrl}${path}`;\n }\n\n const fallbackBase = options.localFallbackBaseUrl ?? 'http://localhost:3000';\n return `${fallbackBase}${path}`;\n };\n\n const getRequestInit = (language: string, namespace: string): RequestInit => {\n if (typeof options.requestInit === 'function') {\n return options.requestInit(language, namespace) ?? {};\n }\n\n return options.requestInit ?? {};\n };\n\n const getCached = (cacheKey: string) => {\n if (options.disableCache) {\n return null;\n }\n\n const entry = localCache.get(cacheKey);\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt < Date.now()) {\n localCache.delete(cacheKey);\n return null;\n }\n\n return entry.data;\n };\n\n const setCached = (cacheKey: string, data: TranslationRecord) => {\n if (options.disableCache) {\n return;\n }\n\n localCache.set(cacheKey, {\n data,\n expiresAt: Date.now() + cacheTtlMs\n });\n };\n\n const loadTranslations: TranslationLoader = async (language, namespace) => {\n const cacheKey = `${language}:${namespace}`;\n const cached = getCached(cacheKey);\n\n if (cached) {\n return cached;\n }\n\n const inFlight = inFlightRequests.get(cacheKey);\n if (inFlight) {\n return inFlight;\n }\n\n const url = buildUrl(language, namespace);\n const requestInit = getRequestInit(language, namespace);\n\n const performRequest = async (attempt: number): Promise<TranslationRecord> => {\n try {\n const response = await fetcher(url, {\n cache: 'no-store',\n ...requestInit\n });\n\n if (!response.ok) {\n throw new Error(\n `[i18n-loaders] Failed to load ${language}/${namespace} (${response.status})`\n );\n }\n\n const data = (await response.json()) as TranslationRecord;\n setCached(cacheKey, data);\n return data;\n } catch (error) {\n // 재시도 가능한 에러인지 확인\n const isRetryable = isRetryableError(error);\n\n if (isRetryable && attempt < retryCount) {\n logger.warn?.(\n `[i18n-loaders] Translation fetch failed (attempt ${attempt + 1}/${retryCount + 1}), retrying...`,\n language,\n namespace,\n error\n );\n \n // 지수 백오프: 각 재시도마다 지연 시간 증가\n const delay = retryDelay * Math.pow(2, attempt);\n await new Promise(resolve => setTimeout(resolve, delay));\n \n return performRequest(attempt + 1);\n }\n\n // 재시도 불가능하거나 재시도 횟수 초과\n logger.warn?.(\n '[i18n-loaders] translation fetch failed',\n language,\n namespace,\n error\n );\n throw error;\n }\n };\n\n const requestPromise = performRequest(0)\n .finally(() => {\n inFlightRequests.delete(cacheKey);\n });\n\n inFlightRequests.set(cacheKey, requestPromise);\n return requestPromise;\n };\n\n /**\n * 특정 언어/네임스페이스의 캐시 무효화\n */\n const invalidate = (language?: string, namespace?: string) => {\n if (language && namespace) {\n // 특정 언어/네임스페이스만 무효화\n const cacheKey = `${language}:${namespace}`;\n localCache.delete(cacheKey);\n logger.log?.('[i18n-loaders] Cache invalidated:', cacheKey);\n } else if (language) {\n // 특정 언어의 모든 네임스페이스 무효화\n const prefix = `${language}:`;\n for (const key of localCache.keys()) {\n if (key.startsWith(prefix)) {\n localCache.delete(key);\n }\n }\n logger.log?.('[i18n-loaders] Cache invalidated for language:', language);\n } else if (namespace) {\n // 특정 네임스페이스의 모든 언어 무효화\n const suffix = `:${namespace}`;\n for (const key of localCache.keys()) {\n if (key.endsWith(suffix)) {\n localCache.delete(key);\n }\n }\n logger.log?.('[i18n-loaders] Cache invalidated for namespace:', namespace);\n } else {\n // 전체 캐시 무효화\n clear();\n }\n };\n\n /**\n * 전체 캐시 클리어\n */\n const clear = () => {\n localCache.clear();\n logger.log?.('[i18n-loaders] Cache cleared');\n };\n\n // 개발 모드에서 자동 무효화 설정\n if (autoInvalidateInDev && typeof window !== 'undefined') {\n // 개발 모드에서 페이지 포커스 시 캐시 무효화 (번역 파일 변경 감지)\n window.addEventListener('focus', () => {\n if (process.env.NODE_ENV === 'development') {\n logger.log?.('[i18n-loaders] Development mode: Auto-invalidating cache on focus');\n clear();\n }\n });\n }\n\n // loadTranslations 함수에 invalidate와 clear 메서드 추가\n const loader = loadTranslations as TranslationLoader & {\n invalidate: (language?: string, namespace?: string) => void;\n clear: () => void;\n };\n \n loader.invalidate = invalidate;\n loader.clear = clear;\n\n return loader;\n}\n\n","import { PreloadOptions, TranslationLoader } from './types';\n\nconst defaultLogger = console;\n\nexport async function preloadNamespaces(\n language: string,\n namespaces: string[],\n loader: TranslationLoader,\n options: PreloadOptions = {}\n) {\n const logger = options.logger ?? defaultLogger;\n\n const results = await Promise.allSettled(\n namespaces.map(async (namespace) => {\n await loader(language, namespace);\n return namespace;\n })\n );\n\n const fulfilled = results.filter(\n (result): result is PromiseFulfilledResult<string> =>\n result.status === 'fulfilled'\n );\n const rejected = results.filter(\n (result): result is PromiseRejectedResult => result.status === 'rejected'\n );\n\n if (fulfilled.length > 0) {\n logger.log?.(\n `[i18n-loaders] Preloaded ${fulfilled.length}/${namespaces.length} namespaces for ${language}`\n );\n }\n\n if (rejected.length > 0 && !options.suppressErrors) {\n logger.warn?.(\n `[i18n-loaders] Failed to preload ${rejected.length} namespaces for ${language}`\n );\n }\n\n return {\n fulfilled: fulfilled.map((result) => result.value),\n rejected: rejected.map((result) => result.reason)\n };\n}\n\nexport async function warmFallbackLanguages(\n currentLanguage: string,\n languages: string[],\n namespaces: string[],\n loader: TranslationLoader,\n options: PreloadOptions = {}\n) {\n const targets = languages.filter((language) => language !== currentLanguage);\n if (targets.length === 0) {\n return [];\n }\n\n return Promise.all(\n targets.map((language) =>\n preloadNamespaces(language, namespaces, loader, options)\n )\n );\n}\n\n","import {\n DefaultTranslations,\n TranslationLoader,\n TranslationRecord\n} from './types';\n\nexport function withDefaultTranslations(\n loader: TranslationLoader,\n defaults: DefaultTranslations\n): TranslationLoader {\n return async (language, namespace) => {\n const fallback = defaults[language]?.[namespace];\n\n try {\n const remote = await loader(language, namespace);\n \n // API 응답이 빈 객체이거나 null/undefined인 경우 fallback 반환\n if (!remote || (typeof remote === 'object' && Object.keys(remote).length === 0)) {\n if (fallback) {\n return fallback;\n }\n return remote || {};\n }\n \n if (!fallback) {\n return remote;\n }\n\n return mergeTranslations(fallback, remote);\n } catch (error) {\n if (fallback) {\n return fallback;\n }\n throw error;\n }\n };\n}\n\nfunction mergeTranslations(\n base: TranslationRecord,\n override: TranslationRecord\n): TranslationRecord {\n const result: TranslationRecord = { ...base };\n\n for (const [key, value] of Object.entries(override)) {\n if (isPlainObject(value) && isPlainObject(result[key])) {\n result[key] = mergeTranslations(\n result[key] as TranslationRecord,\n value as TranslationRecord\n );\n continue;\n }\n\n result[key] = value;\n }\n\n return result;\n}\n\nfunction isPlainObject(value: unknown): value is TranslationRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n"]}
|