Skip to content

Commit

Permalink
✨ [feat]: Implement locale customization for each EntityProcessor ins…
Browse files Browse the repository at this point in the history
…tance
  • Loading branch information
brunotot committed Sep 26, 2023
1 parent 5e43667 commit 93dc516
Show file tree
Hide file tree
Showing 69 changed files with 580 additions and 386 deletions.
32 changes: 19 additions & 13 deletions packages/core/dev/playground.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import Localization from "../src/localization";
import EntityProcessor from "../src/reflection/models/entity.processor";
import ValidationMetaService from "../src/reflection/service/impl/reflection.service.validation";
import Required from "../validators/any/Required";
import foreach from "../validators/array/foreach";

class TestClass {
@foreach(Required())
array: string[] = [];
}
Localization.Resolver.configure((locale, msg) => translations[locale][msg]);

const processor = new EntityProcessor(TestClass);
const res = processor.validate({});
//console.log(res);
const translations: any = {
hr: { translation1: `[hr]: Unos je obavezan` },
en: { translation1: `[en]: Field is mandatory` },
it: { translation1: `[it]: Field is mandatory` },
};

//console.log(res.valid);
class TestClass {
@Required("translation1")
str: string = "";
}

const meta = ValidationMetaService.inject(TestClass);
console.log(meta);
//console.log();
//const meta = ValidationMetaService.inject(TestClass);
const processor = new EntityProcessor(TestClass, {
locale: "it",
groups: ["group1"],
});
console.log(processor.validate({}));
processor.locale = "hr";
console.log(processor.validate({}));
debugger;
8 changes: 2 additions & 6 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import makeValidator from "./src/decorators/decorator.facade";
import { Locale, Locales } from "./src/messages/message.types";
import { getLocale, setLocale } from "./src/messages/models/locale";
import Localization from "./src/localization";
import CacheMap from "./src/models/cache.map";
import EntityProcessor from "./src/reflection/models/entity.processor";
import ReflectionDescriptor, {
Expand Down Expand Up @@ -60,7 +59,7 @@ export type {
EvaluatedStrategy,
FieldDescriptorRules,
FieldStrategy,
Locale,
Localization,
MetaStrategy,
ObjectArrayDetailedErrors,
ObjectArraySimpleErrors,
Expand All @@ -81,7 +80,6 @@ export type {

export {
EntityProcessor,
Locales,
ObjectArrayStrat,
ObjectStrat,
PrimitiveArrayStrat,
Expand All @@ -92,8 +90,6 @@ export {
ValidationMetaService,
ValidationStrategy,
getClassFieldNames,
getLocale,
makeValidator,
setLocale,
validators,
};
8 changes: 6 additions & 2 deletions packages/core/src/decorators/decorator.utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Localization from "../localization";
import $ from "../types";
import Decorator from "../types/namespace/decorator.namespace";
import Objects from "../types/namespace/objects.namespace";
Expand All @@ -15,13 +16,16 @@ import Validation from "../types/namespace/validation.namespace";
*/
export function extractMessage<T extends object>(
provider: Decorator.PartialProps<any, T> | undefined,
defaultMessage: string
defaultMessage: string,
locale: Localization.Locale
): string {
if (!provider) return defaultMessage;
const providerType = typeof provider;
const msgNullable = providerType ? provider : provider.message;
const msgNonNull = msgNullable ?? "";
return msgNonNull.length ? msgNonNull : defaultMessage;
return msgNonNull.length
? Localization.Resolver.resolve(locale, msgNonNull)
: defaultMessage;
}

/**
Expand Down
51 changes: 51 additions & 0 deletions packages/core/src/localization/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace Localization {
export type Locale = "en" | "hr" | "de" | "es" | "fr" | "it" | "nl";

/**
* Type definition for a collection of locale-specific messages.
*
* @remarks
* The keys are locale codes (e.g. "en", "de"...), and the values are objects
* in which the key represents a translation identifier while the value
* corresponds to the identifier's localized string.
*/
export type Messages = Record<Locale, Record<string, string>>;

let locale: Localization.Locale = "en";

export function getLocale(): Localization.Locale {
return locale;
}

export function setLocale(localeValue: Localization.Locale) {
locale = localeValue;
}

export namespace Resolver {
const DEFAULT_CONFIGURER: (
locale: Localization.Locale,
message: string
) => string = (_locale, message) => message;

let configurer: (locale: Localization.Locale, message: string) => string =
DEFAULT_CONFIGURER;

export function configure(
handler?: (locale: Localization.Locale, message: string) => string
) {
configurer = handler ?? DEFAULT_CONFIGURER;
}

export function resolve(locale: Locale, message: string) {
try {
return configurer(locale, message);
} catch (error) {
throw new Error(
`An error occurred while resolving \"${message}\" for locale \"${locale}\". To fix, check your Localization.Resolver.configure() implementation.\n\n${error}`
);
}
}
}
}

export default Localization;
60 changes: 60 additions & 0 deletions packages/core/src/localization/service/translation.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Localization from "..";
import * as de from "../translations/de.json";
import * as en from "../translations/en.json";
import * as es from "../translations/es.json";
import * as fr from "../translations/fr.json";
import * as hr from "../translations/hr.json";
import * as it from "../translations/it.json";
import * as nl from "../translations/nl.json";

const localeMessages: Localization.Messages = { hr, de, en, es, fr, it, nl };

/**
* Formats a string by replacing placeholders with provided arguments.
*
* @param str - The string containing placeholders in the form of `{0}`, `{1}`, etc.
* @param args - The values to replace the placeholders with.
* @returns The formatted string with placeholders replaced by the corresponding values from `args`.
*
* @example
* ```typescript
* const formatted = sprintf("Hello, {0}!", "World"); // Output: "Hello, World!"
* ```
*
* @remarks
* If a placeholder's corresponding value is not provided in `args`, the placeholder will remain unchanged in the output string.
*/
function sprintf(str: string, ...args: any[]) {
return str.replace(/{(\d+)}/g, function (match, number) {
return typeof args[number] != "undefined" ? args[number] : match;
});
}

namespace TranslationService {
/**
* Localizes a string based on a key and optional arguments.
*
* @param key - The key corresponding to the localized string.
* @param args - Optional values to replace placeholders in the localized string.
* @returns The localized and formatted string.
*
* @example
* ```typescript
* const greeting = TranslationService.translate("hello", "Bruno"); // Output might be: "Hello, Bruno!"
* ```
*
* @remarks
* The function fetches the current locale using `getLocale()` and retrieves the corresponding message from `localeMessages`.
* It then uses `sprintf` to replace any placeholders in the message with the provided `args`.
*/
export function translate(
locale: Localization.Locale | null,
key: string,
...args: any[]
) {
const service = localeMessages[locale ?? Localization.getLocale()];
return sprintf(service[key], ...args);
}
}

export default TranslationService;
47 changes: 0 additions & 47 deletions packages/core/src/messages/message.factory.ts

This file was deleted.

17 changes: 0 additions & 17 deletions packages/core/src/messages/message.types.ts

This file was deleted.

64 changes: 0 additions & 64 deletions packages/core/src/messages/models/error-messages.ts

This file was deleted.

13 changes: 0 additions & 13 deletions packages/core/src/messages/models/locale.ts

This file was deleted.

20 changes: 0 additions & 20 deletions packages/core/src/messages/models/messages.ts

This file was deleted.

Loading

0 comments on commit 93dc516

Please sign in to comment.