Initial commit

This commit is contained in:
Igor Martyniuk
2026-06-25 15:53:49 +03:00
commit 1bb0b7e68a
1517 changed files with 218469 additions and 0 deletions
@@ -0,0 +1,13 @@
import type { NameFormatInputType, NameFormatInputTypeCamel, NameFormatInputTypeSnake, NameFormatInputTypeString } from './name-input-types';
export const isSnakeCaseInputType = (input: NameFormatInputType): input is NameFormatInputTypeSnake => {
return typeof input === 'object' && input !== null && 'first_name' in input;
};
export const isCamelCaseInputType = (input: NameFormatInputType): input is NameFormatInputTypeCamel => {
return typeof input === 'object' && input !== null && 'firstName' in input;
};
export const isStringInputType = (input: NameFormatInputType): input is NameFormatInputTypeString => {
return typeof input === 'string';
};
@@ -0,0 +1,40 @@
import { isCamelCaseInputType, isSnakeCaseInputType, isStringInputType } from './check-input-type';
import type { CanonicalName, NameFormatInputType } from './name-input-types';
export function convertInputToCamel(input: NameFormatInputType): CanonicalName {
if (isCamelCaseInputType(input)) {
return {
firstName: input.firstName ?? '',
lastName: input.lastName ?? '',
patronymic: input.patronymic,
prefix: input.prefix,
suffix: input.suffix,
middleName: input.middleName,
};
}
if (isSnakeCaseInputType(input)) {
return {
firstName: input.first_name ?? '',
lastName: input.last_name ?? '',
patronymic: input.patronymic,
middleName: input.middle_name,
prefix: input.prefix,
suffix: input.suffix,
};
}
if (isStringInputType(input)) {
const parts = input.trim().split(/\s+/);
return {
firstName: parts[0] ?? '',
lastName: parts[1] ?? '',
patronymic: parts[2],
middleName: undefined,
prefix: undefined,
suffix: undefined,
};
}
return { firstName: '', lastName: '' };
}
@@ -0,0 +1,78 @@
import { CanonicalName } from './name-input-types';
import { INTL_NAME_TOKENS } from './tokens';
export const UNIVERSAL_NAME_PATTERN = 'UNIVERSAL' as const;
function initial(value?: string) {
return (value?.[0] ?? '').toUpperCase();
}
function valueOrEmpty(v?: string) {
return v ?? '';
}
export function formatNameByPattern(pattern: string, ctx: CanonicalName): string {
if (pattern === UNIVERSAL_NAME_PATTERN) {
return [ctx.firstName, ctx.lastName, ctx.patronymic].filter(Boolean).join(' ').trim();
}
const TOKEN_REGEX = /(Pr|Su|FF|LL|PP|MM|F|L|P|M)/g;
const resolveToken = (token: string): string => {
switch (token) {
case INTL_NAME_TOKENS.FIRST.FULL:
return valueOrEmpty(ctx.firstName);
case INTL_NAME_TOKENS.FIRST.INITIAL:
return initial(ctx.firstName);
case INTL_NAME_TOKENS.LAST.FULL:
return valueOrEmpty(ctx.lastName);
case INTL_NAME_TOKENS.LAST.INITIAL:
return initial(ctx.lastName);
case INTL_NAME_TOKENS.MIDDLE.FULL:
return valueOrEmpty(ctx.middleName);
case INTL_NAME_TOKENS.MIDDLE.INITIAL:
return initial(ctx.middleName);
case INTL_NAME_TOKENS.PATRONYMIC.FULL:
return valueOrEmpty(ctx.patronymic);
case INTL_NAME_TOKENS.PATRONYMIC.INITIAL:
return initial(ctx.patronymic);
case INTL_NAME_TOKENS.PREFIX:
return valueOrEmpty(ctx.prefix);
case INTL_NAME_TOKENS.SUFFIX:
return valueOrEmpty(ctx.suffix);
default:
return '';
}
};
const renderTokensWithPresence = (str: string): { text: string; hasValue: boolean } => {
let hasValue = false;
const text = str.replace(TOKEN_REGEX, (token) => {
const value = resolveToken(token);
if (value !== '') hasValue = true;
return value;
});
return { text, hasValue };
};
let out = pattern;
const blockRe = /\{([^{}]*)\}/g;
while (true) {
const before = out;
out = out.replace(blockRe, (_, inner: string) => {
const { text, hasValue } = renderTokensWithPresence(inner);
return hasValue ? text : '';
});
if (out === before) break;
}
out = renderTokensWithPresence(out).text;
return out.replace(/\s+/g, ' ').trim();
}
+2
View File
@@ -0,0 +1,2 @@
export { intlNameService } from './main';
export { INTL_NAME_TOKENS } from './tokens';
+53
View File
@@ -0,0 +1,53 @@
import { convertInputToCamel } from './convert-input';
import { formatNameByPattern } from './formatter';
import { NameFormatInputType } from './name-input-types';
import { InitIntlNameServiceConfig, IntlNameInstanceInterface, IntlNameLocaleMap } from './types';
export function intlNameService<LM extends IntlNameLocaleMap, FallbackLang extends keyof LM>(
config: InitIntlNameServiceConfig<LM, FallbackLang>,
logger?: Console,
): IntlNameInstanceInterface<LM, FallbackLang> {
let language = config.language;
let localeMap = config.localeMap;
const getPattern = (fmt: string): string => {
const langPattern = localeMap[language]?.formats?.[fmt];
if (langPattern) return langPattern;
const fallbackPattern = localeMap[config.fallbackLanguage]?.formats?.[fmt];
if (fallbackPattern) return fallbackPattern;
return 'UNIVERSAL';
};
return {
setLanguage: (lang) => {
if (!localeMap[lang]) {
logger?.error('Language is not supported. Add it to the supported list.');
return;
}
language = lang;
logger?.log('Set global language: ', lang);
},
setSupportedLanguages: (map) => {
localeMap = map;
logger?.log('Set supported languages: ', map);
},
formatToDisplay: (input: NameFormatInputType, fmt) => {
const name = convertInputToCamel(input);
const pattern = getPattern(fmt);
return formatNameByPattern(pattern, {
firstName: name.firstName,
lastName: name.lastName,
middleName: name.patronymic,
prefix: name.prefix,
suffix: name.suffix,
});
},
toObject: (input: NameFormatInputType) => convertInputToCamel(input),
};
}
@@ -0,0 +1,30 @@
export type NameFormatInputTypeSnake = {
first_name: string;
last_name: string;
patronymic?: string;
middle_name?: string;
prefix?: string;
suffix?: string;
};
export type NameFormatInputTypeCamel = {
firstName: string;
lastName: string;
patronymic?: string;
middleName?: string;
prefix?: string;
suffix?: string;
};
export type NameFormatInputTypeString = string;
export type NameFormatInputType = NameFormatInputTypeSnake | NameFormatInputTypeCamel | NameFormatInputTypeString;
export type CanonicalName = {
firstName: string;
lastName: string;
patronymic?: string;
middleName?: string;
prefix?: string;
suffix?: string;
};
+20
View File
@@ -0,0 +1,20 @@
export const INTL_NAME_TOKENS = {
FIRST: {
INITIAL: 'F',
FULL: 'FF',
},
LAST: {
INITIAL: 'L',
FULL: 'LL',
},
PATRONYMIC: {
INITIAL: 'P',
FULL: 'PP',
},
MIDDLE: {
INITIAL: 'M',
FULL: 'MM',
},
PREFIX: 'Pr',
SUFFIX: 'Su',
} as const;
+23
View File
@@ -0,0 +1,23 @@
import { CanonicalName, NameFormatInputType } from './name-input-types';
export type IntlNameLocaleMap = Record<
string,
{
formats: Record<string, string>;
}
>;
type InferFormatKeys<LM extends IntlNameLocaleMap, FallbackLang extends keyof LM> = keyof LM[FallbackLang]['formats'] & string;
export type InitIntlNameServiceConfig<LM extends IntlNameLocaleMap, FallbackLang extends keyof LM> = {
fallbackLanguage: FallbackLang;
language: keyof LM & string;
localeMap: LM;
};
export type IntlNameInstanceInterface<LM extends IntlNameLocaleMap, FallbackLang extends keyof LM> = {
setLanguage: (lang: keyof LM & string) => void;
setSupportedLanguages: (map: LM) => void;
formatToDisplay: (input: NameFormatInputType, fmt: InferFormatKeys<LM, FallbackLang>) => string;
toObject: (input: NameFormatInputType) => CanonicalName;
};