Flutter에서 다국어 앱(i18n)을 어떻게 구현하나요?

Flutter에서 다국어 앱(국제화, i18n)을 구현하는 방법에는 여러 가지가 있지만, 가장 권장되는 방법은 공식 flutter_localizations 패키지와 intl 패키지를 함께 사용하는 것입니다. 아래에서 단계별로 Flutter 앱에서 다국어 지원을 구현하는 방법을 설명하겠습니다.

1. 필요한 패키지 추가

먼저 pubspec.yaml 파일에 필요한 패키지를 추가합니다:

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: ^0.18.0

flutter:
  generate: true # 자동 생성된 localizations를 사용하기 위해 필요

2. l10n.yaml 설정 파일 생성

프로젝트 루트 디렉토리에 l10n.yaml 파일을 생성하고 다음 내용을 추가합니다:

arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
nullable-getter: false

3. ARB 파일 생성

lib/l10n 디렉토리를 생성하고, 기본 언어(영어)에 대한 app_en.arb 파일을 만듭니다:

{
  "helloWorld": "Hello World",
  "@helloWorld": {
    "description": "The conventional greeting"
  },
  "welcome": "Welcome {name}",
  "@welcome": {
    "description": "Welcome message",
    "placeholders": {
      "name": {
        "type": "String",
        "example": "John"
      }
    }
  },
  "items": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "@items": {
    "description": "Number of items",
    "placeholders": {
      "count": {
        "type": "num",
        "format": "compact"
      }
    }
  }
}

그 후 다른 언어 파일도 생성합니다. 예를 들어, 한국어를 위한 app_ko.arb:

{
  "helloWorld": "안녕 세상",
  "welcome": "{name}님 환영합니다",
  "items": "{count, plural, =0{항목 없음} =1{1개 항목} other{{count}개 항목}}"
}

4. Localizations 설정

main.dart 파일에서 앱의 지원 언어를 설정합니다:

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter i18n Demo',
      // 지원할 언어 목록
      supportedLocales: AppLocalizations.supportedLocales,
      // 로컬라이제이션 대리자
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      // 기본 언어 설정 (장치 언어를 따름)
      localeResolutionCallback: (locale, supportedLocales) {
        // 장치 언어가 지원되지 않는 경우 영어를 기본으로 사용
        for (var supportedLocale in supportedLocales) {
          if (supportedLocale.languageCode == locale?.languageCode) {
            return supportedLocale;
          }
        }
        return supportedLocales.first; // 기본값은 영어
      },
      home: MyHomePage(),
    );
  }
}

5. 생성된 Localizations 사용

위젯에서 생성된 AppLocalizations 클래스를 사용하여 현재 언어에 맞는 문자열을 표시합니다:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 현재 로케일에 대한 AppLocalizations 인스턴스 가져오기
    final localizations = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(
        title: Text(localizations.helloWorld),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(localizations.welcome('John')),
            Text(localizations.items(5)),
          ],
        ),
      ),
    );
  }
}

6. 언어 전환 기능 구현

사용자가 앱 내에서 언어를 변경할 수 있도록 하려면 Provider 패키지나 다른 상태 관리 솔루션을 사용하여 현재 locale을 관리할 수 있습니다:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class LocaleProvider extends ChangeNotifier {
  Locale _locale = Locale('en');

  Locale get locale => _locale;

  void setLocale(Locale locale) {
    _locale = locale;
    notifyListeners();
  }
}

// main.dart
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => LocaleProvider(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<LocaleProvider>(
      builder: (context, localeProvider, child) {
        return MaterialApp(
          title: 'Flutter i18n Demo',
          locale: localeProvider.locale, // 현재 선택된 로케일 사용
          supportedLocales: AppLocalizations.supportedLocales,
          localizationsDelegates: const [
            AppLocalizations.delegate,
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
          ],
          home: MyHomePage(),
        );
      },
    );
  }
}

// 언어 선택 화면
class LanguageSelectionPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final localeProvider = Provider.of<LocaleProvider>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text(AppLocalizations.of(context)!.languageSelection),
      ),
      body: ListView(
        children: [
          ListTile(
            title: Text('English'),
            onTap: () {
              localeProvider.setLocale(Locale('en'));
            },
            trailing: localeProvider.locale.languageCode == 'en'
                ? Icon(Icons.check)
                : null,
          ),
          ListTile(
            title: Text('한국어'),
            onTap: () {
              localeProvider.setLocale(Locale('ko'));
            },
            trailing: localeProvider.locale.languageCode == 'ko'
                ? Icon(Icons.check)
                : null,
          ),
          // 다른 언어 추가...
        ],
      ),
    );
  }
}

7. 날짜, 숫자 등의 형식화

intl 패키지를 사용하여 날짜, 숫자 등을 현재 로케일에 맞게 형식화할 수 있습니다:

import 'package:intl/intl.dart';

// 현재 로케일에 맞는 날짜 형식
String formatDate(BuildContext context, DateTime date) {
  final locale = Localizations.localeOf(context).toString();
  return DateFormat.yMMMd(locale).format(date);
}

// 현재 로케일에 맞는 숫자 형식
String formatNumber(BuildContext context, num number) {
  final locale = Localizations.localeOf(context).toString();
  return NumberFormat.decimalPattern(locale).format(number);
}

// 현재 로케일에 맞는 통화 형식
String formatCurrency(BuildContext context, num amount) {
  final locale = Localizations.localeOf(context).toString();
  return NumberFormat.currency(
    locale: locale,
    symbol: locale == 'ko' ? '₩' : '\$',
  ).format(amount);
}

8. 언어 설정 저장

사용자가 선택한 언어 설정을 shared_preferences와 같은 로컬 저장소에 저장하여 앱 재시작 시에도 유지할 수 있습니다:

import 'package:shared_preferences/shared_preferences.dart';

class LocaleProvider extends ChangeNotifier {
  Locale _locale = Locale('en');

  Locale get locale => _locale;

  // 저장된 언어 설정 로드
  Future<void> loadSavedLocale() async {
    final prefs = await SharedPreferences.getInstance();
    final languageCode = prefs.getString('languageCode');

    if (languageCode != null) {
      _locale = Locale(languageCode);
      notifyListeners();
    }
  }

  // 선택한 언어 설정 저장
  Future<void> setLocale(Locale locale) async {
    if (_locale == locale) return;

    _locale = locale;
    notifyListeners();

    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('languageCode', locale.languageCode);
  }
}

// main.dart에서 앱 시작 시 저장된 설정 로드
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final localeProvider = LocaleProvider();
  await localeProvider.loadSavedLocale();

  runApp(
    ChangeNotifierProvider.value(
      value: localeProvider,
      child: MyApp(),
    ),
  );
}

9. 특정 언어에 대한 리소스 관리

이미지, 아이콘 등 텍스트가 아닌 리소스도 언어에 따라 달라질 수 있습니다. 이런 리소스도 로케일에 따라 관리할 수 있습니다:

class LocalizedAssets {
  static String getImage(BuildContext context, String name) {
    final locale = Localizations.localeOf(context).languageCode;
    return 'assets/images/${locale}/$name';
  }
}

// 사용 예
Image.asset(LocalizedAssets.getImage(context, 'welcome_banner.png'))

10. 효율적인 번역 관리

번역 파일이 많아지면 관리가 복잡해질 수 있습니다. 스프레드시트나 번역 관리 도구를 사용하여 번역을 관리하고 ARB 파일로 내보내는 워크플로우를 구축하면 효율적입니다.

요약

Flutter에서 다국어 앱을 구현하는 주요 단계는 다음과 같습니다:

  1. flutter_localizationsintl 패키지 추가
  2. ARB 파일에 번역 정의
  3. 앱 설정에 localizationsDelegatessupportedLocales 설정
  4. 생성된 AppLocalizations 클래스를 통해 번역된 문자열 사용
  5. 언어 전환 기능 및 사용자 설정 저장 구현
  6. 날짜, 숫자 등의 형식화 처리

이 방법을 통해 Flutter 앱에서 효과적으로 다국어를 지원하고, 사용자에게 지역화된 경험을 제공할 수 있습니다.

results matching ""

    No results matching ""