Skip to main content

Basic Usage of SD Function

The SD (Sonamu Dictionary) function returns translated text matching the current Context’s locale:
import { SD } from "../i18n/sd.generated";

// Simple string
const saveButton = SD("common.save");  // "μ €μž₯" (ko) / "Save" (en)

// Function value (dynamic message)
const message = SD("common.results")(42);  // "42개 결과"
const error = SD("notFound")("User", 123);  // "μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” User ID 123"

Type Safety

Using a non-existent key results in a compile-time error:
SD("common.save")       // βœ… OK
SD("common.savee")      // ❌ Compile error: key 'common.savee' does not exist
SD("typo.key")          // ❌ Compile error

Forcing a Specific Locale

Use SD.locale() to get values for a specific locale regardless of Context:
const EN = SD.locale("en");
const KO = SD.locale("ko");

EN("common.save")  // "Save" (always English)
KO("common.save")  // "μ €μž₯" (always Korean)

// Switch to recipient's locale when sending emails
async function sendEmail(userLocale: string, email: string) {
  const L = SD.locale(userLocale);
  await mailer.send({
    to: email,
    subject: L("email.welcome.subject"),
    body: L("email.welcome.body"),
  });
}

Getting Enum Labels

SD.enumLabels() returns a Proxy with labels for all values of an Enum:
// Enum defined in Entity
// enumLabels: { UserRole: { admin: "κ΄€λ¦¬μž", normal: "일반" } }

const labels = SD.enumLabels("UserRole");
labels["admin"]   // "κ΄€λ¦¬μž"
labels["normal"]  // "일반"

// Using in Select component
<Select
  options={Object.entries(UserRole).map(([key, value]) => ({
    value,
    label: SD.enumLabels("UserRole")[value],
  }))}
/>

Dictionary Writing Patterns

Key Naming Conventions

export default {
  // domain.action or domain.item
  "common.save": "μ €μž₯",
  "common.cancel": "μ·¨μ†Œ",

  // Error messages
  "error.notFound": "찾을 수 μ—†μŠ΅λ‹ˆλ‹€",
  "error.unauthorized": "인증이 ν•„μš”ν•©λ‹ˆλ‹€",

  // Validation messages
  "validation.required": (field: string) => `${field}은(λŠ”) ν•„μˆ˜μž…λ‹ˆλ‹€`,
  "validation.email": "μ˜¬λ°”λ₯Έ 이메일 ν˜•μ‹μ΄ μ•„λ‹™λ‹ˆλ‹€",

  // Page/feature specific
  "login.title": "둜그인",
  "login.submit": "둜그인",
  "dashboard.welcome": "ν™˜μ˜ν•©λ‹ˆλ‹€!",

  // Confirmation messages
  "confirm.delete": "정말 μ‚­μ œν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?",
} as const;

Writing Function Values

Use arrow functions when dynamic values are needed:
export default {
  // Simple interpolation
  "common.results": (count: number) => `${count}개 결과`,

  // Multiple parameters
  "entity.edit": (name: string, id: number) => `${name} μˆ˜μ • (#${id})`,

  // Conditional text
  "items.count": (count: number) =>
    count === 0 ? "ν•­λͺ© μ—†μŒ" : `${count}개 ν•­λͺ©`,

  // Using helper functions
  "validation.required": (field: string) => `${josa(field, "μ€λŠ”")} ν•„μˆ˜μž…λ‹ˆλ‹€`,
  "validation.range": (field: string, min: number, max: number) =>
    `${field}은(λŠ”) ${format.number(min)}~${format.number(max)} 사이여야 ν•©λ‹ˆλ‹€`,
} as const;

Using in Models

import { SD } from "../i18n/sd.generated";

class UserModelClass extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async findById<T extends UserSubsetKey>(subset: T, id: number) {
    const { rows } = await this.findMany(subset, { id, num: 1, page: 1 });
    if (!rows[0]) {
      // i18n error message
      throw new NotFoundException(SD("notFound")("User", id));
    }
    return rows[0];
  }

  @api({ httpMethod: "POST" })
  async register(params: UserRegisterParams) {
    const existing = await this.findOne("A", { email: params.email });
    if (existing) {
      throw new BadRequestException(SD("user.email.duplicate"));
    }
    // ...
  }
}