sonamu.config.ts μ€μ
sonamu.config.tsμμ i18n μ΅μ
μ νμ±νν©λλ€:
import { defineConfig } from "sonamu";
export default defineConfig({
// ... κΈ°ν μ€μ
i18n: {
defaultLocale: "ko", // κΈ°λ³Έ μΈμ΄
supportedLocales: ["ko", "en"], // μ§μ μΈμ΄ λͺ©λ‘
},
});
| μ΅μ
| νμ
| μ€λͺ
|
|---|
defaultLocale | string | κΈ°λ³Έ μΈμ΄ μ½λ (fallback) |
supportedLocales | string[] | μ§μνλ λͺ¨λ μΈμ΄ μ½λ |
λλ ν 리 ꡬ쑰
i18nμ νμ±ννλ©΄ api/src/i18n/ λλ ν λ¦¬κ° νμν©λλ€:
λμ
λ리 νμΌ μμ±
defaultLocale λμ
λ리 (ko.ts)
import { createFormat, josa } from "sonamu/dict";
const format = createFormat("ko");
export default {
// κ³΅ν΅ UI
"common.save": "μ μ₯",
"common.cancel": "μ·¨μ",
"common.delete": "μμ ",
"common.results": (count: number) => `${count}κ° κ²°κ³Ό`,
// μλ¬ λ©μμ§
"error.notFound": "μ°Ύμ μ μμ΅λλ€",
"error.unauthorized": "μΈμ¦μ΄ νμν©λλ€",
// κ²μ¦ λ©μμ§ (ν¨μν)
"validation.required": (field: string) => `${josa(field, "μλ")} νμμ
λλ€`,
"validation.email": "μ¬λ°λ₯Έ μ΄λ©μΌ νμμ΄ μλλλ€",
// λ¨μ ν€λ μ¬μ© κ°λ₯ (λ¨, λ€μμ€νμ΄μ€ ν¨ν΄ κΆμ₯)
notFound: (name: string, id: number) => `μ‘΄μ¬νμ§ μλ ${name} ID ${id}`,
test: (date: Date) => format.date(date),
} as const;
ν€ λ€μ΄λ° κΆμ₯ ν¨ν΄: "λλ©μΈ.νλͺ©" νμμ λ€μμ€νμ΄μ€ ν¨ν΄μ μ¬μ©νλ©΄ ν€λ₯Ό 체κ³μ μΌλ‘
κ΄λ¦¬ν μ μκ³ IDE μλμμ±λ νΈλ¦¬ν©λλ€. - "common.*" - κ³΅ν΅ UI - "error.*" - μλ¬ λ©μμ§ -
"validation.*" - κ²μ¦ λ©μμ§ - "entity.*" - Entity κ΄λ ¨
λ€λ₯Έ locale λμ
λ리 (en.ts)
import { plural } from "sonamu/dict";
import { defineLocale } from "./sd.generated";
export default defineLocale({
// κ³΅ν΅ UI
"common.save": "Save",
"common.cancel": "Cancel",
"common.delete": "Delete",
"common.results": (count: number) =>
plural(count, { one: `${count} result`, other: `${count} results` }),
// μλ¬ λ©μμ§
"error.notFound": "Not found",
"error.unauthorized": "Authentication required",
// κ²μ¦ λ©μμ§
"validation.required": (field: string) => `${field} is required`,
"validation.email": "Invalid email format",
// λ¨μ ν€ (λ€μμ€νμ΄μ€ ν¨ν΄ κΆμ₯)
notFound: (name: string, id: number) => `${name} ID ${id} not found`,
});
defineLocaleμ sd.generated.tsμμ exportλλ©°, defaultLocaleμ ν€λ₯Ό κΈ°μ€μΌλ‘ νμ
체ν¬λ₯Ό
μ 곡ν©λλ€.
Locale μ€μ
νλ‘ νΈμλ (ν΄λΌμ΄μΈνΈ)
sd.generated.tsμλ ν΄λΌμ΄μΈνΈ μΈ‘ locale κ΄λ¦¬λ₯Ό μν setLocaleκ³Ό getCurrentLocale ν¨μκ° ν¬ν¨λ©λλ€:
import { setLocale, getCurrentLocale, SUPPORTED_LOCALES } from "@/i18n/sd.generated";
// locale λ³κ²½
setLocale("en");
// νμ¬ locale νμΈ
const current = getCurrentLocale(); // "en"
sonamu.shared.tsμ axios interceptorκ° λͺ¨λ API μμ²μ Accept-Language ν€λλ₯Ό μλμΌλ‘ μΆκ°ν©λλ€:
// sonamu.shared.ts (μλ μμ±λ¨ β μ§μ μμ±ν νμ μμ)
axios.interceptors.request.use((config) => {
config.headers["Accept-Language"] = getCurrentLocale();
return config;
});
λ°λΌμ setLocale("en")μ νΈμΆν λ€μ λͺ¨λ API μμ²μ Accept-Language: en ν€λλ₯Ό ν¬ν¨ν©λλ€.
λ°±μλ (μλ²)
μμ²λ³λ‘ localeμ μ€μ νλ €λ©΄ λ―Έλ€μ¨μ΄μμ Contextλ₯Ό ꡬμ±ν©λλ€:
// api/src/middlewares/locale.ts
import { Sonamu } from "sonamu";
export async function localeMiddleware(request: FastifyRequest) {
// Accept-Language ν€λ λλ 쿼리 νλΌλ―Έν°μμ locale μΆμΆ
const locale =
request.headers["accept-language"]?.split(",")[0]?.split("-")[0] ||
request.query.locale ||
"ko";
// Contextμ locale μ€μ
const ctx = Sonamu.getContext();
ctx.locale = locale;
}
ν΄λΌμ΄μΈνΈμμ setLocale()λ‘ localeμ μ€μ νλ©΄, axios interceptorκ° Accept-Language ν€λλ₯Ό
μλ μ λ¬νκ³ , μλ²μ locale λ―Έλ€μ¨μ΄κ° μ΄λ₯Ό μ½μ΄ SD() νΈμΆ μ ν΄λΉ μΈμ΄μ λ²μμ λ°νν©λλ€.
λ³λμ μΆκ° μ€μ μμ΄ ν΄λΌμ΄μΈνΈ-μλ² κ° localeμ΄ λκΈ°νλ©λλ€.
μ΄κΈ°ν νμΈ
μ€μ μ΄ μλ£λλ©΄ pnpm syncλ₯Ό μ€ννμ¬ sd.generated.tsκ° μμ±λλμ§ νμΈν©λλ€:
μμ±λ sd.generated.tsμλ λ€μμ΄ ν¬ν¨λ©λλ€:
- Entityμμ μΆμΆν λΌλ²¨ (
entityLabels)
SD() ν¨μ
localizedColumn() ν¨μ
defineLocale() ν¨μ
SD ν¨μ μ¬μ©λ²
λμ
λ리 μμ± λ° μ¬μ©
Entity λΌλ²¨
μλ μΆμΆλλ λΌλ²¨