국제화(i18n) — intl 패키지와 ARB 파일로 다국어 지원

글로벌 앱을 만들거나, 한국어/영어 전환이 필요한 앱이라면 국제화(i18n)는 필수입니다. Flutter의 공식 방법인 flutter_localizations + intl + ARB 파일 조합을 정리해보겠습니다.


설정

YAML
# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: ^0.19.0

flutter:
  generate: true  # 코드 생성 활성화
YAML
# l10n.yaml (프로젝트 루트에 생성)
arb-dir: lib/l10n
template-arb-file: app_ko.arb
output-localization-file: app_localizations.dart

ARB 파일 작성

ARB(Application Resource Bundle)는 JSON 기반의 번역 파일입니다.

JSON
// lib/l10n/app_ko.arb (템플릿 - 한국어)
{
  "@@locale": "ko",
  "appTitle": "내 앱",
  "@appTitle": {
    "description": "앱 제목"
  },
  "hello": "안녕하세요, {name}님!",
  "@hello": {
    "description": "인사말",
    "placeholders": {
      "name": {
        "type": "String",
        "example": "홍길동"
      }
    }
  },
  "itemCount": "{count, plural, =0{항목 없음} =1{1개 항목} other{{count}개 항목}}",
  "@itemCount": {
    "description": "항목 수 (복수형)",
    "placeholders": {
      "count": {
        "type": "int"
      }
    }
  },
  "settings": "설정",
  "logout": "로그아웃",
  "confirmDelete": "정말 삭제하시겠습니까?"
}
JSON
// lib/l10n/app_en.arb (영어)
{
  "@@locale": "en",
  "appTitle": "My App",
  "hello": "Hello, {name}!",
  "itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "settings": "Settings",
  "logout": "Logout",
  "confirmDelete": "Are you sure you want to delete?"
}

코드 생성 및 설정

BASH
# 코드 생성 (자동으로 실행되기도 함)
flutter gen-l10n
DART
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 지원하는 로케일 목록
      supportedLocales: AppLocalizations.supportedLocales,
      // 로케일 델리게이트
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      // 기본 로케일 (선택)
      locale: const Locale('ko'),
      home: const HomeScreen(),
    );
  }
}

번역 사용

DART
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // AppLocalizations 인스턴스 가져오기
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(title: Text(l10n.appTitle)),
      body: Column(
        children: [
          Text(l10n.hello('심정훈')),     // 안녕하세요, 심정훈님!
          Text(l10n.itemCount(0)),         // 항목 없음
          Text(l10n.itemCount(1)),         // 1개 항목
          Text(l10n.itemCount(42)),        // 42개 항목
          Text(l10n.settings),             // 설정
        ],
      ),
    );
  }
}

언어 전환

DART
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Locale _locale = const Locale('ko');

  void _changeLocale(Locale locale) {
    setState(() => _locale = locale);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      locale: _locale,
      supportedLocales: AppLocalizations.supportedLocales,
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      home: HomeScreen(onLocaleChanged: _changeLocale),
    );
  }
}

// 언어 전환 버튼
DropdownButton<Locale>(
  value: currentLocale,
  items: const [
    DropdownMenuItem(value: Locale('ko'), child: Text('한국어')),
    DropdownMenuItem(value: Locale('en'), child: Text('English')),
  ],
  onChanged: (locale) {
    if (locale != null) widget.onLocaleChanged(locale);
  },
)

날짜, 숫자 포맷팅

DART
import 'package:intl/intl.dart';

// 날짜 포맷팅
final now = DateTime.now();
DateFormat.yMMMMd('ko').format(now);  // 2026년 9월 7일
DateFormat.Hm('ko').format(now);      // 14:30
DateFormat('yyyy-MM-dd').format(now);  // 2026-09-07

// 숫자 포맷팅
NumberFormat.currency(locale: 'ko', symbol: '₩').format(29900);
// ₩29,900
NumberFormat.compact(locale: 'ko').format(1234567);
// 123만
NumberFormat.decimalPattern('ko').format(1234567);
// 1,234,567
NumberFormat.percentPattern('ko').format(0.756);
// 76%

ARB 파일 고급 문법

JSON
{
  "gender": "{gender, select, male{그는} female{그녀는} other{그 분은}} 개발자입니다",
  "@gender": {
    "placeholders": {
      "gender": {"type": "String"}
    }
  },
  "lastLogin": "마지막 로그인: {date}",
  "@lastLogin": {
    "placeholders": {
      "date": {
        "type": "DateTime",
        "format": "yMd"
      }
    }
  }
}

팁: Extension으로 간결하게 접근

DART
extension L10nContext on BuildContext {
  AppLocalizations get l10n => AppLocalizations.of(this)!;
}

// 사용
Text(context.l10n.appTitle)  // 더 간결

정리

  • flutter_localizations + intl + ARB 파일이 Flutter 공식 i18n 방법입니다
  • ARB 파일에서 {name} 플레이스홀더, plural, select 등을 지원합니다
  • flutter gen-l10n 명령으로 타입 안전한 코드가 자동 생성됩니다
  • intl 패키지로 날짜, 숫자, 통화 포맷팅도 로케일에 맞게 처리합니다
  • Extension을 활용하면 context.l10n으로 간결하게 접근할 수 있습니다
댓글 로딩 중...