forked from baron/baron-sso
각 프런트의 i18n 연결 및 locale 정리
This commit is contained in:
@@ -1,149 +1,16 @@
|
||||
const LOCALE_STORAGE_KEY = "locale";
|
||||
const DEFAULT_LOCALE = "ko";
|
||||
const SUPPORTED_LOCALES = ["ko", "en"] as const;
|
||||
|
||||
type Locale = (typeof SUPPORTED_LOCALES)[number];
|
||||
|
||||
type TomlValue = string | TomlObject;
|
||||
|
||||
interface TomlObject {
|
||||
[key: string]: TomlValue;
|
||||
}
|
||||
|
||||
function isSupportedLocale(value: string): value is Locale {
|
||||
return (SUPPORTED_LOCALES as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
function parseToml(raw: string): TomlObject {
|
||||
const lines = raw.split(/\r?\n/);
|
||||
const root: TomlObject = {};
|
||||
let currentPath: string[] = [];
|
||||
|
||||
for (const rawLine of lines) {
|
||||
const line = rawLine.trim();
|
||||
if (!line || line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("[") && line.endsWith("]")) {
|
||||
const sectionName = line.slice(1, -1).trim();
|
||||
currentPath = sectionName
|
||||
? sectionName
|
||||
.split(".")
|
||||
.map((part) => part.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
continue;
|
||||
}
|
||||
|
||||
const eqIndex = line.indexOf("=");
|
||||
if (eqIndex === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = line.slice(0, eqIndex).trim();
|
||||
const valueRaw = line.slice(eqIndex + 1).trim();
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = valueRaw;
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
let cursor: TomlObject = root;
|
||||
for (const section of currentPath) {
|
||||
if (!cursor[section] || typeof cursor[section] === "string") {
|
||||
cursor[section] = {};
|
||||
}
|
||||
cursor = cursor[section] as TomlObject;
|
||||
}
|
||||
|
||||
cursor[key] = value;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
function getValue(target: TomlObject, key: string): string | undefined {
|
||||
const parts = key.split(".");
|
||||
let cursor: TomlValue = target;
|
||||
for (const part of parts) {
|
||||
if (typeof cursor !== "object" || cursor === null) {
|
||||
return undefined;
|
||||
}
|
||||
cursor = (cursor as TomlObject)[part];
|
||||
if (cursor === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return typeof cursor === "string" ? cursor : undefined;
|
||||
}
|
||||
|
||||
function detectLocale(): Locale {
|
||||
if (typeof window === "undefined") {
|
||||
return DEFAULT_LOCALE;
|
||||
}
|
||||
|
||||
const stored = window.localStorage.getItem(LOCALE_STORAGE_KEY);
|
||||
if (stored && isSupportedLocale(stored)) {
|
||||
return stored;
|
||||
}
|
||||
|
||||
const pathLocale = window.location.pathname.split("/")[1];
|
||||
if (pathLocale && isSupportedLocale(pathLocale)) {
|
||||
return pathLocale;
|
||||
}
|
||||
|
||||
const browserLang = window.navigator.language.toLowerCase();
|
||||
if (browserLang.startsWith("ko")) {
|
||||
return "ko";
|
||||
}
|
||||
|
||||
return DEFAULT_LOCALE;
|
||||
}
|
||||
import { createTomlTranslator } from "../../../common/core/i18n";
|
||||
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import commonEnRaw from "../../../common/locales/en.toml?raw";
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import commonKoRaw from "../../../common/locales/ko.toml?raw";
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import enRaw from "../locales/en.toml?raw";
|
||||
// Vite ?raw import는 런타임 상수로 번들됩니다.
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import koRaw from "../locales/ko.toml?raw";
|
||||
|
||||
const translations: Record<Locale, TomlObject> = {
|
||||
ko: parseToml(koRaw),
|
||||
en: parseToml(enRaw),
|
||||
};
|
||||
|
||||
function formatTemplate(
|
||||
template: string,
|
||||
vars?: Record<string, string | number>,
|
||||
): string {
|
||||
const normalizedTemplate = template.replace(/\\n/g, "\n");
|
||||
if (!vars) {
|
||||
return normalizedTemplate;
|
||||
}
|
||||
return normalizedTemplate.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => {
|
||||
const value = vars[key];
|
||||
if (value === undefined || value === null) {
|
||||
return match;
|
||||
}
|
||||
return String(value);
|
||||
});
|
||||
}
|
||||
|
||||
export function t(
|
||||
key: string,
|
||||
fallback?: string,
|
||||
vars?: Record<string, string | number>,
|
||||
): string {
|
||||
const locale = detectLocale();
|
||||
const value = getValue(translations[locale], key);
|
||||
if (value && value.length > 0) {
|
||||
return formatTemplate(value, vars);
|
||||
}
|
||||
return formatTemplate(fallback ?? key, vars);
|
||||
}
|
||||
export const t = createTomlTranslator({
|
||||
ko: [commonKoRaw, koRaw],
|
||||
en: [commonEnRaw, enRaw],
|
||||
});
|
||||
|
||||
@@ -300,15 +300,6 @@ no_custom = "No custom fields defined for this tenant."
|
||||
[msg.admin.users.list.registry]
|
||||
count = "Count"
|
||||
|
||||
[msg.common]
|
||||
error = "Error"
|
||||
loading = "Loading..."
|
||||
no_description = "No Description."
|
||||
parsing = "Parsing data..."
|
||||
requesting = "Requesting..."
|
||||
saving = "Saving..."
|
||||
unknown_error = "unknown error"
|
||||
|
||||
[msg.dev]
|
||||
logout_confirm = "Are you sure you want to log out?"
|
||||
|
||||
@@ -1244,75 +1235,6 @@ name = "Name"
|
||||
role = "Role"
|
||||
|
||||
|
||||
[ui.common]
|
||||
add = "Add"
|
||||
all = "All"
|
||||
admin_only = "Admin Only"
|
||||
assign = "Assign"
|
||||
back = "Back"
|
||||
back_to_login = "Back to login"
|
||||
cancel = "Cancel"
|
||||
change_file = "Change File"
|
||||
clear_search = "Clear Search"
|
||||
close = "Close"
|
||||
collapse = "Collapse"
|
||||
confirm = "Confirm"
|
||||
copy = "Copy"
|
||||
create = "Create"
|
||||
delete = "Delete"
|
||||
details = "Details"
|
||||
disabled = "Disabled"
|
||||
edit = "Edit"
|
||||
enabled = "Enabled"
|
||||
export = "Export"
|
||||
fail = "Fail"
|
||||
go_home = "Go Home"
|
||||
view = "View"
|
||||
hyphen = "-"
|
||||
manage = "Manage"
|
||||
na = "N/A"
|
||||
never = "Never"
|
||||
next = "Next"
|
||||
none = "None"
|
||||
page_of = "Page {{page}} of {{total}}"
|
||||
prev = "Prev"
|
||||
previous = "Previous"
|
||||
qr = "QR"
|
||||
reset = "Reset"
|
||||
read_only = "Read Only"
|
||||
refresh = "Refresh"
|
||||
remove = "Remove"
|
||||
resend = "Resend"
|
||||
retry = "Retry"
|
||||
save = "Save"
|
||||
search = "Search"
|
||||
select = "Select"
|
||||
select_file = "Select File"
|
||||
select_placeholder = "Select Placeholder"
|
||||
show_more = "Show More"
|
||||
language = "Language"
|
||||
language_ko = "Korean"
|
||||
language_en = "English"
|
||||
success = "Success"
|
||||
theme_dark = "Dark"
|
||||
theme_light = "Light"
|
||||
theme_toggle = "Theme Toggle"
|
||||
unknown = "Unknown"
|
||||
|
||||
[ui.common.badge]
|
||||
admin_only = "Admin only"
|
||||
command_only = "Command only"
|
||||
system = "System"
|
||||
|
||||
[ui.common.status]
|
||||
active = "Active"
|
||||
blocked = "Blocked"
|
||||
failure = "Failure"
|
||||
inactive = "Inactive"
|
||||
ok = "Ok"
|
||||
pending = "Pending"
|
||||
success = "Success"
|
||||
|
||||
[test]
|
||||
key = "Test"
|
||||
|
||||
|
||||
@@ -300,15 +300,6 @@ no_custom = "이 테넌트에 정의된 커스텀 필드가 없습니다."
|
||||
[msg.admin.users.list.registry]
|
||||
count = "총 {{count}}명의 사용자가 등록되어 있습니다."
|
||||
|
||||
[msg.common]
|
||||
error = "오류가 발생했습니다."
|
||||
loading = "로딩 중..."
|
||||
no_description = "설명이 없습니다."
|
||||
parsing = "데이터 파싱 중..."
|
||||
requesting = "요청 중..."
|
||||
saving = "저장 중..."
|
||||
unknown_error = "알 수 없는 오류"
|
||||
|
||||
[msg.dev]
|
||||
logout_confirm = "로그아웃 하시겠습니까?"
|
||||
|
||||
@@ -1244,75 +1235,6 @@ name = "이름"
|
||||
role = "역할"
|
||||
|
||||
|
||||
[ui.common]
|
||||
add = "추가"
|
||||
all = "전체"
|
||||
admin_only = "관리자 전용"
|
||||
assign = "할당"
|
||||
back = "돌아가기"
|
||||
back_to_login = "로그인으로 돌아가기"
|
||||
cancel = "취소"
|
||||
change_file = "파일 변경"
|
||||
clear_search = "검색 초기화"
|
||||
close = "닫기"
|
||||
collapse = "접기"
|
||||
confirm = "확인"
|
||||
copy = "복사"
|
||||
create = "생성"
|
||||
delete = "삭제"
|
||||
details = "상세정보"
|
||||
disabled = "사용 안 함"
|
||||
edit = "편집"
|
||||
enabled = "사용"
|
||||
export = "내보내기"
|
||||
fail = "실패"
|
||||
go_home = "홈으로"
|
||||
view = "보기"
|
||||
hyphen = "-"
|
||||
manage = "관리"
|
||||
na = "N/A"
|
||||
never = "Never"
|
||||
next = "다음"
|
||||
none = "없음"
|
||||
page_of = "Page {{page}} of {{total}}"
|
||||
prev = "이전"
|
||||
previous = "이전"
|
||||
qr = "QR"
|
||||
reset = "초기화"
|
||||
read_only = "읽기 전용"
|
||||
refresh = "새로고침"
|
||||
remove = "제외"
|
||||
resend = "재발송"
|
||||
retry = "다시 시도"
|
||||
save = "저장"
|
||||
search = "검색"
|
||||
select = "선택"
|
||||
select_file = "파일 선택"
|
||||
select_placeholder = "선택하세요"
|
||||
show_more = "+ 더보기"
|
||||
language = "언어"
|
||||
language_ko = "한국어"
|
||||
language_en = "English"
|
||||
success = "성공"
|
||||
theme_dark = "Dark"
|
||||
theme_light = "Light"
|
||||
theme_toggle = "테마 전환"
|
||||
unknown = "Unknown"
|
||||
|
||||
[ui.common.badge]
|
||||
admin_only = "Admin only"
|
||||
command_only = "Command only"
|
||||
system = "System"
|
||||
|
||||
[ui.common.status]
|
||||
active = "활성"
|
||||
blocked = "차단됨"
|
||||
failure = "실패"
|
||||
inactive = "비활성"
|
||||
ok = "정상"
|
||||
pending = "준비 중"
|
||||
success = "성공"
|
||||
|
||||
[test]
|
||||
key = "테스트"
|
||||
|
||||
|
||||
@@ -314,15 +314,6 @@ no_custom = ""
|
||||
[msg.admin.users.list.registry]
|
||||
count = ""
|
||||
|
||||
[msg.common]
|
||||
error = ""
|
||||
loading = ""
|
||||
no_description = ""
|
||||
parsing = ""
|
||||
requesting = ""
|
||||
saving = ""
|
||||
unknown_error = ""
|
||||
|
||||
[msg.dev]
|
||||
logout_confirm = ""
|
||||
|
||||
@@ -1297,75 +1288,6 @@ name = ""
|
||||
role = ""
|
||||
|
||||
|
||||
[ui.common]
|
||||
add = ""
|
||||
all = ""
|
||||
admin_only = ""
|
||||
assign = ""
|
||||
back = ""
|
||||
back_to_login = ""
|
||||
cancel = ""
|
||||
change_file = ""
|
||||
clear_search = ""
|
||||
close = ""
|
||||
collapse = ""
|
||||
confirm = ""
|
||||
copy = ""
|
||||
create = ""
|
||||
delete = ""
|
||||
details = ""
|
||||
disabled = ""
|
||||
edit = ""
|
||||
enabled = ""
|
||||
export = ""
|
||||
fail = ""
|
||||
go_home = ""
|
||||
view = ""
|
||||
hyphen = ""
|
||||
manage = ""
|
||||
na = ""
|
||||
never = ""
|
||||
next = ""
|
||||
none = ""
|
||||
page_of = ""
|
||||
prev = ""
|
||||
previous = ""
|
||||
qr = ""
|
||||
reset = ""
|
||||
read_only = ""
|
||||
refresh = ""
|
||||
remove = ""
|
||||
resend = ""
|
||||
retry = ""
|
||||
save = ""
|
||||
search = ""
|
||||
select = ""
|
||||
select_file = ""
|
||||
select_placeholder = ""
|
||||
show_more = ""
|
||||
language = ""
|
||||
language_ko = ""
|
||||
language_en = ""
|
||||
success = ""
|
||||
theme_dark = ""
|
||||
theme_light = ""
|
||||
theme_toggle = ""
|
||||
unknown = ""
|
||||
|
||||
[ui.common.badge]
|
||||
admin_only = ""
|
||||
command_only = ""
|
||||
system = ""
|
||||
|
||||
[ui.common.status]
|
||||
active = ""
|
||||
blocked = ""
|
||||
failure = ""
|
||||
inactive = ""
|
||||
ok = ""
|
||||
pending = ""
|
||||
success = ""
|
||||
|
||||
[test]
|
||||
key = ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user