@next-vibe/checker 1.0.11
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/LICENSE +674 -0
- package/.dist/README.md +458 -0
- package/.dist/bin/vibe-runtime.js +55482 -0
- package/.dist/bin/vibe-runtime.js.map +431 -0
- package/LICENSE +10 -0
- package/README.md +458 -0
- package/check.config.ts +989 -0
- package/package.json +129 -0
- package/src/config/constants.ts +9 -0
- package/src/config/debug.ts +30 -0
- package/src/config/env-client.ts +62 -0
- package/src/config/env.ts +59 -0
- package/src/config/i18n/de/index.ts +3 -0
- package/src/config/i18n/en/index.ts +60 -0
- package/src/config/i18n/pl/index.ts +3 -0
- package/src/i18n/core/config.ts +229 -0
- package/src/i18n/core/language-utils.ts +236 -0
- package/src/i18n/core/localization-utils.ts +422 -0
- package/src/i18n/core/scoped-translation.ts +120 -0
- package/src/i18n/core/shared-component.tsx +30 -0
- package/src/i18n/core/shared-translation-utils.ts +97 -0
- package/src/i18n/core/shared.ts +44 -0
- package/src/i18n/core/static-types.ts +72 -0
- package/src/i18n/core/translation-utils.ts +218 -0
- package/src/i18n/de/index.ts +8 -0
- package/src/i18n/en/index.ts +7 -0
- package/src/i18n/index.ts +100 -0
- package/src/i18n/pl/index.ts +7 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoped Translation Factory
|
|
3
|
+
* Creates module-specific translation functions that work only within a defined scope
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { type CountryLanguage, defaultLocale, type Languages } from "./config";
|
|
7
|
+
import { getLanguageFromLocale } from "./language-utils";
|
|
8
|
+
import {
|
|
9
|
+
navigateTranslationObject,
|
|
10
|
+
processTranslationValue,
|
|
11
|
+
} from "./shared-translation-utils";
|
|
12
|
+
import type { TParams } from "./static-types";
|
|
13
|
+
import type { DotNotation } from "./static-types";
|
|
14
|
+
|
|
15
|
+
// this value should never be used at runtime
|
|
16
|
+
export type TranslatedKeyType = "createScopedTranslation-key";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Translation schema type for scoped modules
|
|
20
|
+
* Supports deeply nested structures with recursive type definition
|
|
21
|
+
*/
|
|
22
|
+
export interface ScopedTranslationSchema {
|
|
23
|
+
[key: string]: string | ScopedTranslationSchema;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a scoped translation system for a specific module
|
|
28
|
+
* EN translations are used as the source of truth for type safety
|
|
29
|
+
*
|
|
30
|
+
* @param translationsByLanguage - Object mapping language codes to their translation schemas
|
|
31
|
+
* @returns A simpleT function that works only with the provided translations
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // In src/app/api/[locale]/sms/i18n/index.ts
|
|
35
|
+
* import { createScopedTranslation } from "@/i18n/core/scoped-translation";
|
|
36
|
+
* import { translations as enTranslations } from "./en";
|
|
37
|
+
* import { translations as deTranslations } from "./de";
|
|
38
|
+
* import { translations as plTranslations } from "./pl";
|
|
39
|
+
*
|
|
40
|
+
* export const simpleT = createScopedTranslation({
|
|
41
|
+
* en: enTranslations,
|
|
42
|
+
* de: deTranslations,
|
|
43
|
+
* pl: plTranslations,
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // Usage:
|
|
47
|
+
* const { t } = simpleT("en-GLOBAL");
|
|
48
|
+
* t("sms.error.invalid_phone_format"); // Type-safe based on EN schema
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* Helper type to extract the scoped translation key type from createScopedTranslation return
|
|
52
|
+
*/
|
|
53
|
+
export type ExtractScopedTranslationKey<T> = T extends {
|
|
54
|
+
ScopedTranslationKey: infer K;
|
|
55
|
+
}
|
|
56
|
+
? K
|
|
57
|
+
: never;
|
|
58
|
+
|
|
59
|
+
export function createScopedTranslation<
|
|
60
|
+
const TTranslations extends Record<Languages, ScopedTranslationSchema>,
|
|
61
|
+
>(
|
|
62
|
+
translationsByLanguage: TTranslations,
|
|
63
|
+
): {
|
|
64
|
+
readonly ScopedTranslationKey: DotNotation<TTranslations["en"]>;
|
|
65
|
+
readonly scopedT: (locale: CountryLanguage) => {
|
|
66
|
+
t: (
|
|
67
|
+
key: DotNotation<TTranslations["en"]>,
|
|
68
|
+
params?: TParams,
|
|
69
|
+
) => TranslatedKeyType;
|
|
70
|
+
};
|
|
71
|
+
} {
|
|
72
|
+
return {
|
|
73
|
+
ScopedTranslationKey: undefined as DotNotation<TTranslations["en"]>,
|
|
74
|
+
|
|
75
|
+
scopedT: function simpleT(locale: CountryLanguage): {
|
|
76
|
+
t: (
|
|
77
|
+
key: DotNotation<TTranslations["en"]>,
|
|
78
|
+
params?: TParams,
|
|
79
|
+
) => TranslatedKeyType;
|
|
80
|
+
} {
|
|
81
|
+
return {
|
|
82
|
+
t: (
|
|
83
|
+
key: DotNotation<TTranslations["en"]>,
|
|
84
|
+
params?: TParams,
|
|
85
|
+
): TranslatedKeyType => {
|
|
86
|
+
// Extract language from locale with safety check
|
|
87
|
+
if (!locale || typeof locale !== "string") {
|
|
88
|
+
return key as TranslatedKeyType; // Return the key as fallback
|
|
89
|
+
}
|
|
90
|
+
if (!key || typeof key !== "string") {
|
|
91
|
+
return key as TranslatedKeyType; // Return the key as fallback
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const language = locale.split("-")[0] as Languages;
|
|
95
|
+
const defaultLanguage = getLanguageFromLocale(defaultLocale);
|
|
96
|
+
|
|
97
|
+
// Get translations for the requested language
|
|
98
|
+
const languageTranslations = translationsByLanguage[language];
|
|
99
|
+
const fallbackTranslations = translationsByLanguage[defaultLanguage];
|
|
100
|
+
|
|
101
|
+
// Navigate through the translation object using shared logic
|
|
102
|
+
const keys = (key as string).split(".");
|
|
103
|
+
let value = navigateTranslationObject(languageTranslations, keys);
|
|
104
|
+
|
|
105
|
+
// Try fallback language if value not found
|
|
106
|
+
if (value === undefined && language !== defaultLanguage) {
|
|
107
|
+
value = navigateTranslationObject(fallbackTranslations, keys);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Process the translation value using shared logic (handles parameter replacement)
|
|
111
|
+
return processTranslationValue(
|
|
112
|
+
value,
|
|
113
|
+
key,
|
|
114
|
+
params as TParams,
|
|
115
|
+
) as TranslatedKeyType;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import type { CountryLanguage } from "./config";
|
|
4
|
+
import { _simpleT } from "./shared";
|
|
5
|
+
import type { TranslationKey, TranslationValue } from "./static-types";
|
|
6
|
+
import { renderTranslation } from "./translation-utils";
|
|
7
|
+
|
|
8
|
+
interface SimpleTranslationProps<K extends TranslationKey> {
|
|
9
|
+
lang: CountryLanguage;
|
|
10
|
+
i18nKey: K;
|
|
11
|
+
values?: TranslationValue<K> extends string
|
|
12
|
+
? Record<string, string | number>
|
|
13
|
+
: never;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Server-side translation component
|
|
18
|
+
* Use this for translations in server components
|
|
19
|
+
*/
|
|
20
|
+
export default function SimpleT<K extends TranslationKey>({
|
|
21
|
+
lang,
|
|
22
|
+
i18nKey,
|
|
23
|
+
values,
|
|
24
|
+
}: SimpleTranslationProps<K>): ReactNode {
|
|
25
|
+
// Use simpleT directly which already uses the shared utility
|
|
26
|
+
const translatedValue = _simpleT(lang, i18nKey, values);
|
|
27
|
+
|
|
28
|
+
// Use the shared rendering logic
|
|
29
|
+
return renderTranslation(translatedValue, i18nKey);
|
|
30
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Translation Utilities
|
|
3
|
+
* Core logic for navigating translation objects and processing values
|
|
4
|
+
* Used by both global and scoped translation systems
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { translationsKeyMode } from "@/config/debug";
|
|
8
|
+
|
|
9
|
+
import type { TParams } from "./static-types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Nested translation value type supporting deep nesting
|
|
13
|
+
*/
|
|
14
|
+
export type NestedValue = string | { [key: string]: NestedValue };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Navigate through a translation object using an array of keys
|
|
18
|
+
* This is the core navigation logic shared between global and scoped translations
|
|
19
|
+
*/
|
|
20
|
+
export function navigateTranslationObject(
|
|
21
|
+
startValue: Record<string, NestedValue>,
|
|
22
|
+
keys: string[],
|
|
23
|
+
): NestedValue | undefined {
|
|
24
|
+
let value: NestedValue | undefined = startValue as NestedValue;
|
|
25
|
+
|
|
26
|
+
for (const k of keys) {
|
|
27
|
+
if (value === undefined || value === null) {
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Handle array access
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
const index = Number(k);
|
|
34
|
+
if (!Number.isNaN(index) && index >= 0 && index < value.length) {
|
|
35
|
+
value = value[index] as NestedValue;
|
|
36
|
+
} else {
|
|
37
|
+
value = undefined;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Handle object access
|
|
42
|
+
else if (typeof value === "object" && k in value) {
|
|
43
|
+
value = (value as Record<string, NestedValue>)[k];
|
|
44
|
+
} else {
|
|
45
|
+
value = undefined;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Process translation value and handle parameters
|
|
55
|
+
* Shared logic for parameter replacement in translation strings
|
|
56
|
+
*/
|
|
57
|
+
export function processTranslationValue<K extends string>(
|
|
58
|
+
value: NestedValue | undefined,
|
|
59
|
+
key: K,
|
|
60
|
+
params?: TParams,
|
|
61
|
+
): string {
|
|
62
|
+
// If value is undefined, return the key as fallback
|
|
63
|
+
if (value === undefined || value === null) {
|
|
64
|
+
return `${key}${params ? ` (${JSON.stringify(params)})` : ""}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// If value is a string, process parameters
|
|
68
|
+
if (typeof value === "string") {
|
|
69
|
+
let translationValue: string = value;
|
|
70
|
+
if (params) {
|
|
71
|
+
Object.entries(params).forEach(([paramKey, paramValue]) => {
|
|
72
|
+
translationValue = translationValue.replaceAll(
|
|
73
|
+
new RegExp(`{{${paramKey}}}`, "g"),
|
|
74
|
+
String(paramValue),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Handle translation key mode for debugging
|
|
80
|
+
if (translationsKeyMode) {
|
|
81
|
+
// Return URLs (remote and local) as-is
|
|
82
|
+
if (
|
|
83
|
+
translationValue.startsWith("http://") ||
|
|
84
|
+
translationValue.startsWith("/") ||
|
|
85
|
+
translationValue.startsWith("https://")
|
|
86
|
+
) {
|
|
87
|
+
return translationValue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return params ? `${key} (${Object.keys(params).join(", ")})` : `${key}`;
|
|
91
|
+
}
|
|
92
|
+
return translationValue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Handle non-string values - return the key as fallback
|
|
96
|
+
return key;
|
|
97
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CountryLanguage, Languages } from "./config";
|
|
2
|
+
import type { TFunction, TParams, TranslationKey } from "./static-types";
|
|
3
|
+
import { translateKey } from "./translation-utils";
|
|
4
|
+
|
|
5
|
+
// Server-side translation function
|
|
6
|
+
export function simpleT(locale: CountryLanguage): {
|
|
7
|
+
t: TFunction;
|
|
8
|
+
} {
|
|
9
|
+
return {
|
|
10
|
+
t: <K extends TranslationKey>(key: K, params?: TParams): string => {
|
|
11
|
+
return _simpleT(locale, key, params);
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Server-side translation function
|
|
17
|
+
export function _simpleT<K extends TranslationKey>(
|
|
18
|
+
locale: CountryLanguage,
|
|
19
|
+
key: K,
|
|
20
|
+
params?: TParams,
|
|
21
|
+
): string {
|
|
22
|
+
// Extract language from locale with safety check
|
|
23
|
+
if (!locale || typeof locale !== "string") {
|
|
24
|
+
// oxlint-disable-next-line no-console
|
|
25
|
+
console.error("Invalid locale provided to translation function:", locale);
|
|
26
|
+
return key; // Return the key as fallback
|
|
27
|
+
}
|
|
28
|
+
if (!key || typeof key !== "string") {
|
|
29
|
+
// oxlint-disable-next-line no-console
|
|
30
|
+
console.error("Invalid key provided to translation function:", key);
|
|
31
|
+
return key; // Return the key as fallback
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const language = locale.split("-")[0] as Languages;
|
|
35
|
+
|
|
36
|
+
// Use the shared translation utility with server context
|
|
37
|
+
return translateKey(
|
|
38
|
+
key,
|
|
39
|
+
language,
|
|
40
|
+
params,
|
|
41
|
+
undefined, // Use default fallback language
|
|
42
|
+
"server",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ExplicitObjectType } from "next-vibe/shared/types/utils";
|
|
2
|
+
|
|
3
|
+
import type { translationsKeyTypesafety } from "@/config/debug";
|
|
4
|
+
|
|
5
|
+
import type { TranslationSchema } from "./config";
|
|
6
|
+
import type { TranslatedKeyType } from "./scoped-translation";
|
|
7
|
+
|
|
8
|
+
export interface TranslationElement {
|
|
9
|
+
[key: string]: string | number | string[] | TranslationElement;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Utility type to create dot-notation paths for nested objects
|
|
13
|
+
type DotPrefix<T extends string> = T extends "" ? "" : `.${T}`;
|
|
14
|
+
|
|
15
|
+
export type DotNotation<T> = (
|
|
16
|
+
T extends ExplicitObjectType
|
|
17
|
+
? {
|
|
18
|
+
[K in Exclude<keyof T, symbol>]: `${K}${DotPrefix<DotNotation<T[K]>>}`;
|
|
19
|
+
}[Exclude<keyof T, symbol>]
|
|
20
|
+
: ""
|
|
21
|
+
) extends infer D
|
|
22
|
+
? Extract<D, string>
|
|
23
|
+
: never;
|
|
24
|
+
|
|
25
|
+
// Type for all possible translation keys
|
|
26
|
+
export type TranslationKey = typeof translationsKeyTypesafety extends true
|
|
27
|
+
? _TranslationKey
|
|
28
|
+
: string;
|
|
29
|
+
export type _TranslationKey =
|
|
30
|
+
| DotNotation<TranslationSchema>
|
|
31
|
+
| TranslatedKeyType;
|
|
32
|
+
|
|
33
|
+
// Utility type to get the type of a value at a specific path
|
|
34
|
+
type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}`
|
|
35
|
+
? K extends keyof T
|
|
36
|
+
? PathValue<T[K], Rest>
|
|
37
|
+
: never
|
|
38
|
+
: P extends keyof T
|
|
39
|
+
? T[P]
|
|
40
|
+
: never;
|
|
41
|
+
|
|
42
|
+
// Type for getting the value type of a translation key
|
|
43
|
+
export type TranslationValue<K extends TranslationKey> = PathValue<
|
|
44
|
+
TranslationSchema,
|
|
45
|
+
K
|
|
46
|
+
>;
|
|
47
|
+
|
|
48
|
+
export type TParams = Record<string, string | number>;
|
|
49
|
+
export type TFunction = <K extends TranslationKey>(
|
|
50
|
+
key: K,
|
|
51
|
+
params?: TParams,
|
|
52
|
+
) => string;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Utility type to extract the scoped key type from a scopedT function or scoped translation object
|
|
56
|
+
*
|
|
57
|
+
* For best results, use with the full createScopedTranslation result which includes ScopedTranslationKey:
|
|
58
|
+
* @example
|
|
59
|
+
* import { scopedTranslation } from "@/app/api/[locale]/contact/i18n";
|
|
60
|
+
* type ContactKeys = ExtractScopedKeyType<typeof scopedTranslation>;
|
|
61
|
+
* // ContactKeys = "title" | "description" | "form.label" | "form.fields.name.label" | ...
|
|
62
|
+
*
|
|
63
|
+
* For scopedT functions, use the string type from the scoped translation object's ScopedTranslationKey:
|
|
64
|
+
* @example
|
|
65
|
+
* type ContactKeys = typeof scopedTranslation.ScopedTranslationKey;
|
|
66
|
+
*/
|
|
67
|
+
export type ExtractScopedKeyType<T> =
|
|
68
|
+
// If T has ScopedTranslationKey property (from createScopedTranslation result), extract it
|
|
69
|
+
T extends { ScopedTranslationKey: infer K extends string }
|
|
70
|
+
? K
|
|
71
|
+
: // Otherwise return never - use the ScopedTranslationKey property directly instead
|
|
72
|
+
never;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { Environment } from "next-vibe/shared/utils";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import { envClient } from "@/config/env-client";
|
|
5
|
+
|
|
6
|
+
import { languageConfig } from "..";
|
|
7
|
+
import type { Countries, CountryLanguage, Languages } from "./config";
|
|
8
|
+
import { defaultLocaleConfig, translations } from "./config";
|
|
9
|
+
import type {
|
|
10
|
+
TParams,
|
|
11
|
+
TranslationElement,
|
|
12
|
+
TranslationKey,
|
|
13
|
+
} from "./static-types";
|
|
14
|
+
|
|
15
|
+
// ================================================================================
|
|
16
|
+
// TRANSLATION UTILITIES
|
|
17
|
+
// ================================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Centralized translation error handling
|
|
21
|
+
* This ensures we only log each error once and in a consistent format
|
|
22
|
+
*/
|
|
23
|
+
export function logTranslationError(
|
|
24
|
+
errorType: "missing" | "invalid_type" | "fallback_missing",
|
|
25
|
+
key: string,
|
|
26
|
+
context?: string,
|
|
27
|
+
): void {
|
|
28
|
+
if (!languageConfig.debug || envClient.NODE_ENV === Environment.PRODUCTION) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const prefix = context ? `[${context}] ` : "";
|
|
33
|
+
|
|
34
|
+
// Using process.stderr for error logging in development
|
|
35
|
+
// In production, these would be caught by error monitoring
|
|
36
|
+
switch (errorType) {
|
|
37
|
+
case "missing":
|
|
38
|
+
if (typeof process !== "undefined" && process.stderr) {
|
|
39
|
+
process.stderr.write(`${prefix}Translation key not found: ${key}\n`);
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
case "invalid_type":
|
|
43
|
+
if (typeof process !== "undefined" && process.stderr) {
|
|
44
|
+
process.stderr.write(
|
|
45
|
+
`${prefix}Translation key "${key}" has invalid type (expected string)\n`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
case "fallback_missing":
|
|
50
|
+
if (typeof process !== "undefined" && process.stderr) {
|
|
51
|
+
process.stderr.write(
|
|
52
|
+
`${prefix}Translation key not found in fallback language: ${key}\n`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Navigate through a translation object using an array of keys
|
|
61
|
+
*/
|
|
62
|
+
function navigateTranslationPath(
|
|
63
|
+
startValue: TranslationElement,
|
|
64
|
+
keys: string[],
|
|
65
|
+
fullKey: string,
|
|
66
|
+
language: Languages,
|
|
67
|
+
fallbackLanguage: Languages,
|
|
68
|
+
isUsingFallback: boolean,
|
|
69
|
+
context?: string,
|
|
70
|
+
): TranslationElement | string | undefined {
|
|
71
|
+
// Import shared navigation logic
|
|
72
|
+
const { navigateTranslationObject } = require("./shared-translation-utils");
|
|
73
|
+
const value = navigateTranslationObject(startValue, keys);
|
|
74
|
+
|
|
75
|
+
// Handle error logging for missing keys
|
|
76
|
+
if (
|
|
77
|
+
value === undefined &&
|
|
78
|
+
(isUsingFallback || language === fallbackLanguage)
|
|
79
|
+
) {
|
|
80
|
+
logTranslationError(
|
|
81
|
+
isUsingFallback ? "fallback_missing" : "missing",
|
|
82
|
+
fullKey,
|
|
83
|
+
context,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return value as TranslationElement | string | undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Try to get translation from a specific language
|
|
92
|
+
*/
|
|
93
|
+
function tryGetTranslation<K extends TranslationKey>(
|
|
94
|
+
key: K,
|
|
95
|
+
language: Languages,
|
|
96
|
+
isUsingFallback: boolean,
|
|
97
|
+
fallbackLanguage: Languages,
|
|
98
|
+
context?: string,
|
|
99
|
+
): TranslationElement | string | undefined {
|
|
100
|
+
const keys = key?.split(".");
|
|
101
|
+
const translationsForLanguage = translations[language];
|
|
102
|
+
|
|
103
|
+
if (!translationsForLanguage) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return navigateTranslationPath(
|
|
108
|
+
translationsForLanguage,
|
|
109
|
+
keys,
|
|
110
|
+
key,
|
|
111
|
+
language,
|
|
112
|
+
fallbackLanguage,
|
|
113
|
+
isUsingFallback,
|
|
114
|
+
context,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get translation value from nested object using dot notation
|
|
120
|
+
*/
|
|
121
|
+
export function getTranslationValue<K extends TranslationKey>(
|
|
122
|
+
key: K,
|
|
123
|
+
language: Languages,
|
|
124
|
+
fallbackLanguage: Languages = defaultLocaleConfig.language,
|
|
125
|
+
context?: string,
|
|
126
|
+
): TranslationElement | string | undefined {
|
|
127
|
+
// Try with the specified language first
|
|
128
|
+
const value = tryGetTranslation(
|
|
129
|
+
key,
|
|
130
|
+
language,
|
|
131
|
+
false,
|
|
132
|
+
fallbackLanguage,
|
|
133
|
+
context,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// If translation not found and not already using fallback, try fallback language
|
|
137
|
+
if (value === undefined && language !== fallbackLanguage) {
|
|
138
|
+
return tryGetTranslation(
|
|
139
|
+
key,
|
|
140
|
+
fallbackLanguage,
|
|
141
|
+
true,
|
|
142
|
+
fallbackLanguage,
|
|
143
|
+
context,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Process translation value and handle parameters
|
|
152
|
+
*/
|
|
153
|
+
export function processTranslationValue<K extends TranslationKey>(
|
|
154
|
+
value: TranslationElement | string | undefined,
|
|
155
|
+
key: K,
|
|
156
|
+
params?: TParams,
|
|
157
|
+
context?: string,
|
|
158
|
+
): string {
|
|
159
|
+
// Import shared processing logic
|
|
160
|
+
const {
|
|
161
|
+
processTranslationValue: sharedProcess,
|
|
162
|
+
} = require("./shared-translation-utils");
|
|
163
|
+
const result = sharedProcess(value, key, params, context);
|
|
164
|
+
|
|
165
|
+
// Log error if value was not a string (only for global translations)
|
|
166
|
+
if (value !== undefined && value !== null && typeof value !== "string") {
|
|
167
|
+
logTranslationError("invalid_type", key, context);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Main translation function that combines getting and processing the value
|
|
175
|
+
*/
|
|
176
|
+
export function translateKey<K extends TranslationKey>(
|
|
177
|
+
key: K,
|
|
178
|
+
language: Languages,
|
|
179
|
+
params?: TParams,
|
|
180
|
+
fallbackLanguage?: Languages,
|
|
181
|
+
context?: string,
|
|
182
|
+
): string {
|
|
183
|
+
// Use hardcoded fallback to avoid circular dependency during initialization
|
|
184
|
+
const actualFallbackLanguage = fallbackLanguage ?? "en";
|
|
185
|
+
const value = getTranslationValue(
|
|
186
|
+
key,
|
|
187
|
+
language,
|
|
188
|
+
actualFallbackLanguage,
|
|
189
|
+
context,
|
|
190
|
+
);
|
|
191
|
+
return processTranslationValue(value, key, params, context);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Shared component rendering logic for translation components
|
|
196
|
+
* This can be used by both client and server components
|
|
197
|
+
*/
|
|
198
|
+
export function renderTranslation<K extends TranslationKey>(
|
|
199
|
+
translatedValue: string | undefined,
|
|
200
|
+
key: K,
|
|
201
|
+
): ReactNode {
|
|
202
|
+
// If the translation is empty or not a string, show the key as fallback
|
|
203
|
+
if (!translatedValue) {
|
|
204
|
+
// We don't log here because the error would have already been logged
|
|
205
|
+
// in getTranslationValue or processTranslationValue
|
|
206
|
+
return key;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return translatedValue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function getCountryFromLocale(locale: CountryLanguage): Countries {
|
|
213
|
+
return locale.split("-")[1] as Countries;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function getLanguageFromLocale(locale: CountryLanguage): Languages {
|
|
217
|
+
return locale.split("-")[0] as Languages;
|
|
218
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { translations as apiTranslations } from "../../app/i18n/de";
|
|
2
|
+
import { translations as configTranslations } from "../../config/i18n/de";
|
|
3
|
+
import type { translations as enTranslations } from "../en";
|
|
4
|
+
|
|
5
|
+
export const translations: typeof enTranslations = {
|
|
6
|
+
app: apiTranslations,
|
|
7
|
+
config: configTranslations,
|
|
8
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { LanguageConfig, LanguageDefaults } from "./core/config";
|
|
2
|
+
import { translations as deTranslations } from "./de";
|
|
3
|
+
import { translations as enTranslations } from "./en";
|
|
4
|
+
import { translations as plTranslations } from "./pl";
|
|
5
|
+
|
|
6
|
+
// ----------------
|
|
7
|
+
// CONFIGURATION
|
|
8
|
+
// ----------------
|
|
9
|
+
export const languageDefaults = {
|
|
10
|
+
country: "GLOBAL" as const,
|
|
11
|
+
currency: "USD",
|
|
12
|
+
language: "en" as const,
|
|
13
|
+
translations: enTranslations,
|
|
14
|
+
} satisfies LanguageDefaults<typeof enTranslations>;
|
|
15
|
+
|
|
16
|
+
export const allTranslations = {
|
|
17
|
+
de: deTranslations,
|
|
18
|
+
pl: plTranslations,
|
|
19
|
+
en: enTranslations,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const languageConfig = {
|
|
23
|
+
debug: false as boolean,
|
|
24
|
+
countries: {
|
|
25
|
+
DE: "DE" as const,
|
|
26
|
+
PL: "PL" as const,
|
|
27
|
+
US: "US" as const,
|
|
28
|
+
GLOBAL: "GLOBAL" as const,
|
|
29
|
+
},
|
|
30
|
+
countriesArr: ["DE", "PL", "US", "GLOBAL"] as const,
|
|
31
|
+
|
|
32
|
+
currencies: {
|
|
33
|
+
EUR: "EUR" as const,
|
|
34
|
+
USD: "USD" as const,
|
|
35
|
+
PLN: "PLN" as const,
|
|
36
|
+
},
|
|
37
|
+
currenciesArr: ["EUR", "USD", "PLN"] as const,
|
|
38
|
+
|
|
39
|
+
languages: {
|
|
40
|
+
DE: "de" as const,
|
|
41
|
+
PL: "pl" as const,
|
|
42
|
+
EN: "en" as const,
|
|
43
|
+
},
|
|
44
|
+
languagesArr: ["de", "pl", "en"] as const,
|
|
45
|
+
|
|
46
|
+
mappings: {
|
|
47
|
+
currencyByCountry: {
|
|
48
|
+
DE: "EUR",
|
|
49
|
+
PL: "PLN",
|
|
50
|
+
US: "USD",
|
|
51
|
+
GLOBAL: "USD",
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
languageByCountry: {
|
|
55
|
+
DE: "de",
|
|
56
|
+
PL: "pl",
|
|
57
|
+
US: "en",
|
|
58
|
+
GLOBAL: "en",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
countryInfo: {
|
|
63
|
+
DE: {
|
|
64
|
+
code: "DE",
|
|
65
|
+
name: "Deutschland",
|
|
66
|
+
language: "de",
|
|
67
|
+
langName: "Deutsch",
|
|
68
|
+
flag: "π©πͺ",
|
|
69
|
+
currency: "EUR",
|
|
70
|
+
symbol: "β¬",
|
|
71
|
+
},
|
|
72
|
+
PL: {
|
|
73
|
+
code: "PL",
|
|
74
|
+
name: "Polska",
|
|
75
|
+
language: "pl",
|
|
76
|
+
langName: "Polski",
|
|
77
|
+
flag: "π΅π±",
|
|
78
|
+
currency: "PLN",
|
|
79
|
+
symbol: "zΕ",
|
|
80
|
+
},
|
|
81
|
+
US: {
|
|
82
|
+
code: "US",
|
|
83
|
+
name: "United States",
|
|
84
|
+
language: "en",
|
|
85
|
+
langName: "English",
|
|
86
|
+
flag: "πΊπΈ",
|
|
87
|
+
currency: "USD",
|
|
88
|
+
symbol: "$",
|
|
89
|
+
},
|
|
90
|
+
GLOBAL: {
|
|
91
|
+
code: "GLOBAL",
|
|
92
|
+
name: "Global",
|
|
93
|
+
language: "en",
|
|
94
|
+
langName: "English",
|
|
95
|
+
flag: "π",
|
|
96
|
+
currency: "USD",
|
|
97
|
+
symbol: "$",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
} satisfies LanguageConfig;
|