[flutter] 플러터에서의 국제화(i18n) 처리 방법

플러터는 다국어 지원을 위한 국제화(i18n) 처리를 간단하게 구현할 수 있습니다. 플러터에서는 intl 패키지를 사용하여 어플리케이션의 문자열을 다국어로 변환할 수 있습니다.

1. intl 패키지 추가하기

먼저, pubspec.yaml 파일에 intl 패키지를 추가해야 합니다. 아래의 코드를 dependencies 섹션에 추가해 주세요.

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: ^0.17.0

그리고 터미널에서 flutter pub get 명령어를 실행하여 패키지를 가져옵니다.

2. arb 파일 생성하기

국제화를 위해 문자열을 저장할 arb 파일을 생성해야 합니다. arb 파일은 JSON 포맷으로 이루어져 있으며, 다국어 문자열을 저장합니다. lib 디렉토리에 l10n 디렉토리를 생성하고, 그 안에 app_localizations.arb 파일을 생성해 주세요.

app_localizations.arb 파일 내부에는 다음과 같은 형식으로 문자열과 해당 언어의 번역을 작성합니다. 예를 들면:

{
  "hello": "Hello",
  "@hello": {
    "description": "The word 'hello'",
    "type": "text",
    "placeholders": {}
  },
  "welcome": "Welcome {name}",
  "@welcome": {
    "description": "A welcome message with a name parameter",
    "type": "text",
    "placeholders": {
      "name": {}
    }
  }
}

위의 예제에서는 hellowelcome이라는 두 개의 문자열을 다국어로 처리하고 있습니다. welcome 문자열은 이름을 포함하기 때문에 placeholders를 사용하였습니다.

3. arb 파일을 dart 파일로 변환하기

이제 생성한 arb 파일을 dart 파일로 변환해야 합니다. 터미널에서 다음 명령어를 실행하여 변환을 수행합니다.

flutter pub pub run intl_translation:extract_to_arb --output-dir=lib/l10n lib/localizations.dart

변환 후, lib 디렉토리 내에 l10n 디렉토리에 app_localizations.dart 파일이 생성됩니다.

4. dart 파일에서 다국어 처리하기

이제 변환한 app_localizations.dart 파일에서 다국어 처리를 할 수 있습니다. lib 폴더에 localizations.dart 파일을 생성하고 다음과 같이 작성해주세요.

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';
import 'package:intl/message_lookup_by_library.dart' as messages;
import 'package:intl/message_lookup_by_library.dart' show MessageLookupByLibrary;

import 'l10n/app_localizations.dart';

typedef Future<dynamic> LibraryLoader();
Map<String, LibraryLoader> _deferredLibraries = {
  'ko': () => Future.value(null)
};

class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
  const AppLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) => ['ko'].contains(locale.languageCode);

  @override
  Future<AppLocalizations> load(Locale locale) {
    final String lang = locale.languageCode.toLowerCase();
    final String country = locale.countryCode.toLowerCase();

    Intl.defaultLocale = "$lang_$country";
    return initializeMessages(lang).then((_) {
      return Future<AppLocalizations>.value(lookupMessages(lang));
    });
  }

  @override
  bool shouldReload(AppLocalizationsDelegate old) => false;
}

LocaleListResolutionCallback localeListResolution(
    {required List<String> supportedLanguages}) {
  return (List<Locale>? preferredLocales, List<Locale>? supportedLocales) {
    Locale? preferredLocale;
    for (final Locale locale in preferredLocales ?? <Locale>[]) {
      if (locale.languageCode == "ko") {
        preferredLocale = locale;
        break;
      }
    }

    if (preferredLocale != null) {
      return preferredLocale;
    }

    for (final Locale locale in preferredLocales ?? <Locale>[]) {
      if (locale.languageCode == "en") {
        preferredLocale = locale;
        break;
      }
    }

    if (preferredLocale != null) {
      return preferredLocale;
    }

    // Fallback to the first supported locale
    return supportedLocales!.first;
  };
}

typedef LocaleResolutionCallback = Locale Function(
    Locale? locale, Iterable<Locale> supportedLocales);

Map<String, LocaleResolutionCallback> _localeResolutionCallbacks =
    <String, LocaleResolutionCallback>{
  '': localeListResolution(supportedLanguages: ['ko', 'en']),
};

@Deprecated('Use `ExampleLocalizations` from the generated file `l10n/app_localizations.dart`. ')
typedef ExampleLocalizations = AppLocalizations;

class AppLocalizations {
  AppLocalizations(Locale locale)
      : localeName = Intl.canonicalizedLocale(locale.toString());

  final String localeName;

  static const LocalizationsDelegate<AppLocalizations> delegate =
      AppLocalizationsDelegate();

  static Future<AppLocalizations> load(Locale locale) {
    return initializeMessages(locale.toString()).then((_) {
      return AppLocalizations(locale);
    });
  }

  static AppLocalizations of(BuildContext context) {
    return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
  }

  static Future<AppLocalizations> init(Locale locale) async {
    final String localeName = Intl.canonicalizedLocale(locale.toString());

    await initializeMessages(locale.toString());
    Intl.defaultLocale = localeName;

    return AppLocalizations(locale);
  }

  String get hello {
    return Intl.message(
      'Hello',
      name: 'hello',
      desc: 'The word \'hello\'',
      locale: localeName,
    );
  }

  String welcome(String name) {
    return Intl.message(
      'Welcome $name',
      name: 'welcome',
      desc: 'A welcome message with a name parameter',
      locale: localeName,
      args: [name],
    );
  }
}

위의 코드는 lib/localizations.dart에 해당하는 부분입니다. 이 코드는 intl 패키지를 이용하여 다국어를 처리하는 코드입니다.

5. 언어 변경 기능 추가하기

이제 언어 변경 기능을 추가해야 합니다. 예를 들어 앱의 설정 화면에서 사용자가 원하는 언어를 선택할 수 있도록 구현할 수 있습니다.

먼저 main.dart 파일을 다음과 같이 수정해 주세요.

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_i18n/l10n/app_localizations.dart';
import 'package:flutter_i18n/l10n/localizations.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Internationalization Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
      localizationsDelegates: [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('ko', 'KR'),
        const Locale('en', 'US'),
      ],
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final AppLocalizations appLocalizations = AppLocalizations.of(context);
    return Scaffold(
      appBar: AppBar(
        title: Text(appLocalizations.hello),
      ),
      body: Center(
        child: Text(
          appLocalizations.welcome("John"),
          style: TextStyle(fontSize: 20),
        ),
      ),
    );
  }
}

위의 예제에서는 언어 변경을 위해 GlobalMaterialLocalizations.delegateGlobalWidgetsLocalizations.delegatelocalizationsDelegates에 추가하였습니다. 사용자가 원하는 언어로 앱을 실행하면 자동으로 해당 언어로 표시됩니다.

6. 번역 추가하기

새로운 언어로 번역을 추가하려면, app_localizations.arb 파일 내에 언어 코드에 해당하는 부분을 추가해야 합니다. 예를 들어 한국어로 번역을 추가하려면 다음과 같이 작성해 주세요.

{
  "@@locale": "ko",
  "hello": "안녕하세요",
  "welcome": "{name}님 환영합니다"
}

이제 앱을 실행하여 언어 변경 기능이 원활하게 동작하는지 확인해보세요. 사용자의 언어 설정에 따라 앱의 문자열이 자동으로 번역되는 것을 확인할 수 있습니다.

참고 자료