Skip to main content

Flutter 현지화 완벽 가이드

ARB 파일부터 RTL 지원까지 easy_localization으로 Flutter 앱을 현지화하고, 모든 언어의 복수형을 처리하고, AI로 번역을 자동화하세요.

1

easy_localization 설치

pubspec.yaml에 easy_localization 패키지를 추가하세요. ARB/JSON 파일, 복수형, context 확장을 지원하는 가장 인기 있는 Flutter i18n 패키지예요. 날짜, 숫자, 텍스트 방향의 로케일 인식 서식을 위해 SDK의 flutter_localizations도 추가하세요.

easy_localization은 번역에 간결하게 접근하도록 context.tr(), 'key'.tr() 같은 context 확장을 제공해요. 개발 중 로케일 로드, 저장, 번역 파일 핫 리로드를 처리해요.
pubspec.yaml
dependencies:
  easy_localization: ^3.0.7
  flutter_localizations:
    sdk: flutter
2

ARB 번역 파일 생성

Application Resource Bundle(ARB) 파일은 Flutter의 표준 현지화 형식이에요. 각 파일에는 키-값 쌍과 플레이스홀더, 복수형 규칙, 번역가용 문맥을 설명하는 선택적 메타데이터가 있어요. assets/translations 디렉터리에 로케일마다 파일을 하나씩 만드세요.

assets/translations/en.arb
{
  "@@locale": "en",
  "appTitle": "My App",
  "@appTitle": {
    "description": "The title of the application"
  },
  "greeting": "Hello, {name}!",
  "@greeting": {
    "description": "Greeting with user name",
    "placeholders": {
      "name": {
        "type": "String",
        "example": "Alice"
      }
    }
  },
  "itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "@itemCount": {
    "description": "Number of items in the cart",
    "placeholders": {
      "count": {
        "type": "num",
        "format": "compact"
      }
    }
  }
}
@greeting 같은 @ 접두사 키는 위 문자열을 설명하는 메타데이터예요. 플레이스홀더 형식과 예시를 포함해 번역가가 정확히 번역할 수 있게 하세요. 메타데이터 키는 런타임에 제거되므로 추가 오버헤드가 없어요.
3

앱 구성

EasyLocalization 위젯으로 앱을 감싸세요. 로케일 상태를 관리하고 애셋 파일에서 번역을 로드하며 MaterialApp에 필요한 로케일 대리자를 제공해요. 필수 대리자 속성 세 가지는 localizationsDelegates, supportedLocales, locale이며 모두 context 확장으로 사용할 수 있어요.

lib/main.dart
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();

  runApp(
    EasyLocalization(
      supportedLocales: [
        Locale('en'),
        Locale('ar'),
        Locale('ja'),
        Locale('de'),
      ],
      path: 'assets/translations',  // Path to your ARB/JSON files
      fallbackLocale: Locale('en'),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      home: HomePage(),
    );
  }
}
"Easy Localization not initialized" 오류는 runApp() 전에 EasyLocalization.ensureInitialized()를 호출하지 않았다는 뜻이에요. WidgetsFlutterBinding.ensureInitialized() 후, runApp() 전에 await해야 해요.
4

위젯 번역

문자열 키에 .tr() 확장 메서드를 사용해 모든 위젯에서 번역된 텍스트를 가져오세요. 복수형에는 개수 값과 함께 .plural()을 사용하세요. easy_localization은 문자열 확장 구문('key'.tr())과 context 메서드 구문(context.tr('key'))을 모두 제공해요.

lib/widgets/home_page.dart
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('appTitle'.tr()),   // Simple key
      ),
      body: Column(
        children: [
          // String with variable interpolation
          Text('greeting'.tr(args: ['Alice'])),

          // Named arguments
          Text('greeting'.tr(namedArgs: {'name': 'Alice'})),

          // Plural form
          Text('itemCount'.plural(3)),
        ],
      ),
    );
  }
}
번역된 텍스트 대신 원시 키가 반환되면 세 가지를 확인하세요. 1) MaterialApp이 EasyLocalization 위젯을 감싸는 것이 아니라 EasyLocalization 위젯이 MaterialApp을 감싸는지. 2) 번역 파일이 pubspec.yaml의 assets 아래에 선언됐는지. 3) EasyLocalization의 파일 경로가 실제 디렉터리 구조와 일치하는지.
5

복수형 및 변수 처리

Flutter는 iOS, Android, 웹과 같은 표준인 ICU MessageFormat을 복수형에 사용해요. ARB 파일에 {count, plural, ...} 구문으로 복수형을 정의하세요. 각 언어에는 CLDR 복수형 규칙에 따른 자체 형식이 필요해요. 아랍어에는 6가지, 러시아어에는 4가지, 일본어에는 1가지가 있어요.

Plural forms by language
// English: 3 useful forms (=0, =1, other)
"itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}"

// Arabic: 6 forms (=0, =1, =2, few, many, other)
"itemCount": "{count, plural, =0{لا عناصر} =1{عنصر واحد} =2{عنصران} few{{count} عناصر} many{{count} عنصرًا} other{{count} عنصر}}"

// Japanese: 1 form (other)
"itemCount": "{count, plural, other{{count}個のアイテム}}"

// German: 2 forms (=1, other)
"itemCount": "{count, plural, =1{1 Artikel} other{{count} Artikel}}"

// Russian: 3 forms (one, few, many)
"itemCount": "{count, plural, =0{Нет товаров} one{{count} товар} few{{count} товара} many{{count} товаров} other{{count} товаров}}"
Variables and named arguments
// Simple variable
"welcome": "Welcome, {name}!"

// Multiple variables
"orderStatus": "Order #{orderId} — {status}"

// Variable in plural context
"unreadMessages": "{count, plural, =0{No unread messages} =1{1 unread message from {sender}} other{{count} unread messages}}"

// In widgets:
Text('welcome'.tr(namedArgs: {'name': userName}));
Text('orderStatus'.tr(namedArgs: {'orderId': '1234', 'status': 'Shipped'}));
Text('unreadMessages'.plural(count, namedArgs: {'sender': senderName}));
if (count == 1)로 단수형/복수형 로직을 하드코딩하지 마세요. 프랑스어 같은 언어는 0을 단수로 취급해요. 러시아어, 폴란드어, 아랍어에는 영어에 전혀 없는 복수형이 있어요. 항상 ICU 복수형 구문을 사용하고 프레임워크가 올바른 형식을 선택하게 하세요.
6

RTL 언어 지원

Flutter는 로케일이 아랍어, 히브리어, 페르시아어, 우르두어 같은 오른쪽에서 왼쪽으로 쓰는 언어이면 전체 레이아웃을 자동으로 반전해요. 올바르게 반전하려면 코드에서 방향을 인식하는 위젯과 속성을 사용해야 해요. 하드코딩한 left/right를 start/end로 바꾸세요.

lib/widgets/adaptive_layout.dart
import 'package:flutter/material.dart';

class AdaptiveLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    return Scaffold(
      body: Row(
        children: [
          // Use start/end instead of left/right
          Expanded(
            child: Padding(
              padding: EdgeInsetsDirectional.only(
                start: 16.0,  // Leading edge (left in LTR, right in RTL)
                end: 8.0,     // Trailing edge
              ),
              child: Text('content'.tr()),
            ),
          ),
          // Flip icons for RTL
          Icon(
            isRtl ? Icons.arrow_back : Icons.arrow_forward,
          ),
        ],
      ),
    );
  }
}
RTL-aware widget patterns
// Use Directional widgets for RTL-aware layouts
EdgeInsetsDirectional.only(start: 16, end: 8)   // Not EdgeInsets.only(left: 16, right: 8)
AlignmentDirectional.centerStart                  // Not Alignment.centerLeft
BorderRadiusDirectional.only(topStart: Radius.circular(8))

// Automatically mirrors when locale changes to Arabic, Hebrew, etc.
// No conditional logic needed — Flutter handles text direction from locale
앱 로케일을 잠시 아랍어(Locale('ar'))로 설정해 RTL을 테스트하세요. Flutter가 전체 UI를 반전해 내비게이션 드로어가 오른쪽에서 열리고, 뒤로 가기 화살표가 뒤집히며, 텍스트가 오른쪽에 정렬돼요. 맞춤 레이아웃도 올바르게 반전하도록 EdgeInsetsDirectional, AlignmentDirectional, BorderRadiusDirectional을 사용하세요.
7

스마트 로케일 폴백 체인

기본적으로 pt-BR 번역이 없으면 Flutter가 쓸 수 있는 pt-PT 번역을 건너뛰고 영어로 바로 폴백해요. locale_chain 패키지는 구성 가능한 폴백 체인으로 이 문제를 해결해요. 한 줄만 설정하면 되고 마이그레이션도 필요 없으며 기존 .tr() 호출이 그대로 작동해요.

lib/main.dart with LocaleChain
import 'package:locale_chain/locale_chain.dart';
import 'package:locale_chain/easy_localization.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();
  LocaleChain.configure();  // Enable smart fallback chains

  runApp(
    EasyLocalization(
      supportedLocales: [
        Locale('en'),
        Locale('pt', 'BR'),
        Locale('pt', 'PT'),
        Locale('es'),
        Locale('es', 'MX'),
      ],
      path: 'assets/translations',
      assetLoader: LocaleChainAssetLoader(  // Swap in the chain loader
        baseLoader: RootBundleAssetLoader(),
      ),
      fallbackLocale: Locale('en'),
      child: MyApp(),
    ),
  );
}

// Result: pt-BR user sees pt-PT translations when pt-BR keys are missing,
// instead of falling back directly to English.
Custom fallback configuration
// Override specific chains while keeping built-in defaults
LocaleChain.configure(
  fallbacks: {
    'pt-BR': ['pt-PT', 'pt'],      // pt-BR → pt-PT → pt → en
    'es-MX': ['es-419', 'es'],     // es-MX → es-419 → es → en
    'fr-CA': ['fr'],               // fr-CA → fr → en
  },
);

// Or replace all defaults with your own chains
LocaleChain.configure(
  fallbacks: {
    'zh-Hant-HK': ['zh-Hant-TW', 'zh-Hans'],
    'pt-BR': ['pt-PT', 'pt'],
  },
  mergeDefaults: false,  // Only your chains, no built-in defaults
);
locale_chain은 포르투갈어, 스페인어, 프랑스어, 독일어, 이탈리아어, 네덜란드어, 노르웨이어, 말레이어 지역 변형의 내장 폴백 체인을 제공해요. pt-BR 사용자는 영어보다 먼저 pt-PT 콘텐츠를 보고 es-MX 사용자는 기본 로케일 전에 es-419, es 순으로 보게 돼요.
8

번역 자동화

현지화 설정을 마쳤다면 AI로 ARB 파일을 번역하세요. IDE에서 AI 어시스턴트에게 원문 ARB 파일 번역을 요청하거나 CI/CD 파이프라인에서 i18n Agent CLI를 사용하세요. ARB 메타데이터(플레이스홀더, 설명)는 번역 품질을 높이는 문맥을 제공해요.

Terminal
# In your IDE, ask your AI assistant:
> Translate assets/translations/en.arb to Arabic, Japanese, and German

✓ ar.arb created (1.4s)
✓ ja.arb created (1.2s)
✓ de.arb created (1.1s)

# Or use the CLI in CI/CD:
npx i18n-agent translate assets/translations/en.arb --lang ar,ja,de
번역은 점진적으로 진행하세요. 원문 ARB 파일에 새 키를 추가하면 모든 파일을 다시 생성하지 말고 변경된 부분만 번역하세요. 사람이 검토한 기존 번역과 ARB 메타데이터를 유지할 수 있어요.

번역 품질 자동화

i18n-validate를 사용해 누락된 키와 손상된 플레이스홀더를 배포 전에 찾아내세요. 실제 번역이 준비되기 전에 i18n-pseudo의 의사 번역으로 UI를 테스트하세요.

흔한 실수

런타임에 번역 파일을 찾을 수 없음

ARB/JSON 파일은 pubspec.yaml의 assets 섹션에 선언해야 해요. 선언하지 않으면 디스크에 파일이 있어도 Flutter가 런타임에 찾지 못해요. assets: - assets/translations/를 추가하세요.

잘못된 ARB 파일 구문

ARB 파일은 엄격한 JSON이므로 후행 쉼표, 작은따옴표, 주석을 사용할 수 없어요. 구문 오류 하나가 전체 파일 로드를 오류 표시 없이 막아요. 번역 문제를 디버깅하기 전에 JSON 린터로 ARB 파일을 검증하세요.

핫 리로드 후 번역 변경 사항 미표시

easy_localization은 번역을 메모리에 캐시해요. 새 키를 추가하거나 기존 번역을 바꾸면 적용을 위해 핫 리로드가 아닌 완전한 핫 재시작이 필요할 수 있어요. 개발 중 번역 파일을 수정한 뒤 핫 재시작(Shift+R)을 사용하세요.

하드코딩된 Left/Right로 인한 RTL 오류

EdgeInsetsDirectional.only(start: 16) 대신 EdgeInsets.only(left: 16)을 사용하면 Flutter가 RTL 언어에서 레이아웃을 반전하지 못해요. 코드베이스에서 Directional 접미사가 없는 EdgeInsets, Alignment, BorderRadius를 검색하세요.

권장 파일 구조

Project Structure
my_flutter_app/
├── assets/
│   └── translations/
│       ├── en.arb          # Source language (English)
│       ├── ar.arb          # Arabic (with 6 plural forms)
│       ├── de.arb          # German
│       ├── ja.arb          # Japanese
│       ├── pt-BR.arb       # Brazilian Portuguese
│       └── pt-PT.arb       # European Portuguese
├── lib/
│   ├── main.dart           # App entry with EasyLocalization
│   ├── app.dart            # MaterialApp with locale delegates
│   └── widgets/
│       └── language_switcher.dart
├── pubspec.yaml            # Dependencies
└── analysis_options.yaml

지금 i18n Agent 사용해 보기

번역 파일을 여기에 드롭

JSON, YAML, PO, XML, CSV, Markdown, Properties

또는 클릭하여 파일 선택

대상 언어

가입 불필요즉시 견적

locale_chain을 활용한 로케일 폴백

pt-BR 같은 지역 로케일에 번역 키가 없으면 Flutter는 상위 로케일 pt를 먼저 확인하지 않고 템플릿 언어로 바로 이동해요.

Terminal
flutter pub add locale_chain
Configuration
import 'package:locale_chain/locale_chain.dart';

LocaleChain.configure(fallbacks: {
  'pt-BR': ['pt', 'en'],
  'zh-Hant-HK': ['zh-Hant', 'zh', 'en'],
});

LocaleChainAssetLoader(baseLoader: RootBundleAssetLoader());

지원 프레임워크와 내장 체인 75개의 전체 목록은 로케일 폴백 가이드에서 확인하세요. Learn more →

자주 묻는 질문