๋ค๊ตญ์ด์ ์ฑ ์์ด์ฝ/์คํ๋์
์ง๊ธ๊น์ง ๋ง๋ ์ฑ์ ๊ธ์๋ ์ ๋ถ ์ฝ๋์ ์ง์ ์ ํ ์๋ค. Text('์๋
ํ์ธ์')์ฒ๋ผ. ์์ด ์ฌ์ฉ์๋ฅผ ์ํ ๋ฒ์ ์ด ํ์ํด์ง๋ฉด ๊ฐ์ฅ ๋จผ์ ๋ ์ค๋ฅด๋ ๋ฐฉ๋ฒ์ ์ด๋ ๋ค.
Widget build(BuildContext context) {
final isKorean = Localizations.localeOf(context).languageCode == 'ko';
return Text(isKorean ? '์๋
ํ์ธ์' : 'Hello'); // ๋ฌธ๊ตฌ๋ง๋ค ์ด๋ฐ ๋ถ๊ธฐ๋ฅผ ๋จ๋ค
}๋ฌธ๊ตฌ๊ฐ ์ด ๊ฐ์ผ ๋๋ ๋ฒํด๋ค. ๋ฐฑ ๊ฐ๊ฐ ๋๊ณ , ์ผ๋ณธ์ด๋ฅผ ์ถ๊ฐํ๋ ์๊ฐ ๋ชจ๋ Text๋ฅผ ๋ค์ ์ด์ด์ ๋ถ๊ธฐ๋ฅผ ๊ณ ์ณ์ผ ํ๋ค. ๋ฒ์ญ๊ฐ์๊ฒ "์ด ํ์ผ๋ง ๋ฒ์ญํด ์ฃผ์ธ์"๋ผ๊ณ ๋งก๊ธธ ์๋ ์๋ค. ๋ฌธ๊ตฌ๋ ์ฝ๋์์ ๋ผ์ด๋ด์ ์ธ์ด๋ณ ํ์ผ์ ๋ฐ๋ก ๋ชจ์๋๋ ๊ฒ์ด ๋ฐฉ๋ฒ์ด๋ค. ์ด๊ฑธ ์ง์ญํ(localization, l10n)๋ผ๊ณ ํ๋ค.
1๋จ๊ณ: ์ค์น์ ์ค์
flutter pub add flutter_localizations --sdk=flutter
flutter pub add intl:anypubspec.yaml์ flutter: ์๋์ ์๋ ์์ฑ์ ์ผ ๋ค.
flutter:
generate: trueํ๋ก์ ํธ ๋งจ ์ ํด๋์ l10n.yaml์ ๋ง๋ ๋ค.
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dartarb-dir: ๋ฒ์ญ ํ์ผ์ ๋ ํด๋๋คtemplate-arb-file: ๊ธฐ์ค์ด ๋๋ ์ธ์ด ํ์ผ์ด๋ค. ๋ค๋ฅธ ์ธ์ด ํ์ผ์ ์ด ํ์ผ์ ํค๋ฅผ ๋ฐ๋ผ๊ฐ๋ค
2๋จ๊ณ: ๋ฒ์ญ ํ์ผ(ARB) ์ฐ๊ธฐ
lib/l10n/app_en.arb๋ JSON ๋ชจ์์ด๋ค. ํค๊ฐ ์ด๋ฆ์ด๊ณ ๊ฐ์ด ๋ฌธ๊ตฌ๋ค.
{
"appTitle": "My App",
"hello": "Hello, {name}!",
"@hello": {
"placeholders": {
"name": { "type": "String" }
}
},
"itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
"@itemCount": {
"placeholders": {
"count": { "type": "int" }
}
}
}lib/l10n/app_ko.arb๋ ๊ฐ์ ํค์ ํ๊ตญ์ด ๋ฌธ๊ตฌ๋ฅผ ์ด๋ค. ํ์ผ ์ด๋ฆ์ _ko๊ฐ ์ธ์ด๋ฅผ ๊ฐ๋ฆฌํจ๋ค.
{
"appTitle": "๋ด ์ฑ",
"hello": "{name}๋, ์๋
ํ์ธ์!",
"itemCount": "{count}๊ฐ"
}{name}: ๋ฌธ๊ตฌ ์์ ๊ฐ์ ๋ผ์ ๋ฃ๋ ์๋ฆฌ(placeholder)๋ค.@hello์์ ๊ทธ ํ์ ์ ์๋ ค์ค๋ค{count, plural, ...}: ๊ฐ์์ ๋ฐ๋ผ ๋ฌธ์ฅ์ด ๋ฌ๋ผ์ง๋ ์์ด ๊ฐ์ ์ธ์ด๋ฅผ ์ํ ๋ณต์ํ ๋ฌธ๋ฒ์ด๋ค. ํ๊ตญ์ด๋ ๋จ์์ ๋ณต์ ๊ตฌ๋ถ์ด ์์ด์{count}๊ฐ๋ก ์ถฉ๋ถํ๋ค- ์ธ์ด๋ฅผ ๋ ์ถ๊ฐํ๋ ค๋ฉด
app_ja.arb์ฒ๋ผ ํ์ผ๋ง ํ๋ ๋ ๋ง๋ ๋ค
3๋จ๊ณ: ์ฝ๋ ์์ฑ๊ณผ ์ฐ๊ฒฐ
flutter gen-l10n์ด ๋ช
๋ น์ด ARB ํ์ผ์ ์ฝ์ด์ AppLocalizations ํด๋์ค๋ฅผ ๋ง๋ ๋ค. ์์ JSON ๊ธ์์ ๋ณธ ์ฝ๋ ์์ฑ๊ณผ ๊ฐ์ ๋ฐ์์ด๋ค. ์ด์ MaterialApp์ ์ฐ๊ฒฐํ๋ค.
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const HomePage(),
)๋ฌธ๊ตฌ๋ AppLocalizations.of(context)๋ก ๊บผ๋ธ๋ค.
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Column(
children: [
Text(l10n.appTitle),
Text(l10n.hello('ํ๊ธธ๋')), // placeholder๋ ํจ์์ ์ธ์๊ฐ ๋๋ค
Text(l10n.itemCount(3)),
],
);
}ํค ์ด๋ฆ์ด ๊ณง ๋ฉ์๋ ์ด๋ฆ์ด๋ผ์ ์คํ๋ฅผ ๋ด๋ฉด ์ปดํ์ผ ๋ ๋นจ๊ฐ ์ค์ด ๋ฌ๋ค. ๋ฌธ์์ด ํค๋ก ์ฐพ๋ ๋ฐฉ์๊ณผ์ ๊ฐ์ฅ ํฐ ์ฐจ์ด๋ค. ๊ธฐ๊ธฐ์ ์ธ์ด๊ฐ ํ๊ตญ์ด๋ฉด ํ๊ตญ์ด, ์์ด๋ฉด ์์ด ๋ฌธ๊ตฌ๊ฐ ๋์จ๋ค. ์ง์ํ์ง ์๋ ์ธ์ด๋ ํ ํ๋ฆฟ ํ์ผ์ ์ฒซ ๋ฒ์งธ ์ง์ ์ธ์ด๋ก ๋์๊ฐ๋ค.
of(context)๋ Theme.of(context)์ ๊ฐ์ ๋ฐฉ์์ด๋ผ, MaterialApp ์๋์ ์์ ฏ์์๋ง ์ธ ์ ์๋ค. MaterialApp์ ๋ง๋๋ build์ context๋ก ๋ถ๋ฅด๋ฉด ์๋ก ์ฌ๋ผ๊ฐ๋ ๋ชป ์ฐพ๋ ๋ฌธ์ ๊ฐ ์ฌ๊ธฐ์๋ ๋๊ฐ์ด ์๊ธด๋ค. ์์ฑ๋ ํ์ผ์ importํ๋ ๊ฒฝ๋ก๋ Flutter ๋ฒ์ ์ ๋ฐ๋ผ ๋ค๋ฅด๋, flutter gen-l10n์ ์คํํ ๋ค ์๋ํฐ์ ์๋ import๋ฅผ ๋ฐ๋ฅธ๋ค. ์๋ํฐ๊ฐ !๊ฐ ํ์ ์๋ค๊ณ ์๋ ค์ฃผ๋ฉด ์ง์ด๋ค.
์ฝ์ง ํฌ์ธํธ 1: AppLocalizations๋ฅผ ์ฐพ์ ์ ์๋ค
๊ฐ์ฅ ํํ ์๋ฌ๋ค. ์์ธ์ ๋ณดํต ์ธ ๊ฐ์ง๋ค.
flutter: generate: true๋ฅผpubspec.yaml์ ์ ๋ฃ์๋คl10n.yaml์ ๊ฒฝ๋ก๊ฐ ํ๋ ธ๊ฑฐ๋, ARB ํ์ผ์ดarb-dir์ ์ ํ ํด๋์ ์๋ค- ์์ฑ ๋ช ๋ น์ ์์ง ์ ๋๋ ธ๋ค
flutter gen-l10n์ด๋ flutter pub get์ ํ ๋ฒ ๋๋ฆฌ๋ฉด ํ๋ฆฌ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ARB ํ์ผ์ ๊ณ ์น ๋ค์๋ ๋ค์ ์์ฑํด์ผ ์ฝ๋์ ๋ฐ์๋๋ค. ์ ํค๋ฅผ ์ถ๊ฐํ๋๋ฐ l10n.xxx๊ฐ ๋นจ๊ฐ ์ค์ด๋ผ๋ฉด ์์ฑ์ ์ ํ ๊ฒ์ด๋ค.
์ฝ์ง ํฌ์ธํธ 2: iOS์์๋ ์ง์ ์ธ์ด๋ฅผ ํ ๋ฒ ๋ ์๋ ค์ผ ํ๋ค
Flutter ์ชฝ ์ค์ ๋ง์ผ๋ก๋ iOS ์์คํ
์ด "์ด ์ฑ์ ํ๊ตญ์ด๋ฅผ ์ง์ํ๋ค"๊ณ ์ธ์ํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค. ios/Runner/Info.plist์ CFBundleLocalizations์ ์ง์ํ๋ ์ธ์ด ์ฝ๋(en, ko ๋ฑ)๋ฅผ ์ ์ด์ค๋ค.
๋ ์ง์ ์ซ์๋ ์ธ์ด์ ๋ง์ถ๋ค
๋ฌธ๊ตฌ๋ง ๋ฐ๊พผ๋ค๊ณ ๋์ด ์๋๋ค. ๊ฐ์ ๋ ์ง๋ ๋๋ผ๋ง๋ค ํ๊ธฐ๊ฐ ๋ค๋ฅด๋ค. intl ํจํค์ง๊ฐ ์ด๋ฅผ ๋ง์ถฐ์ค๋ค.
import 'package:intl/intl.dart';
final date = DateFormat.yMMMd('ko').format(DateTime.now()); // 2026๋
10์ 8์ผ
final price = NumberFormat.currency(locale: 'ko_KR', symbol: 'โฉ').format(12000); // โฉ12,000๋ ์ง์ ๊ธ์ก์ toString()์ผ๋ก ์ง์ ์ด์ด ๋ถ์ด์ง ์๊ณ ํฌ๋งทํฐ๋ฅผ ๊ฑฐ์น๋ค.
์ฑ ์์์ ์ธ์ด ๋ฐ๊พธ๊ธฐ
๊ธฐ๊ธฐ ์ค์ ๊ณผ ๋ค๋ฅด๊ฒ ์ฑ ์์์ ์ธ์ด๋ฅผ ๊ณ ๋ฅด๊ฒ ํ๋ ค๋ฉด ํ
๋ง ๊ธ์ themeMode์ ๊ฐ์ ๋ฐฉ์์ด๋ค. MaterialApp์ locale์ ๊ฐ์ ๋ฃ๊ณ setState๋ก ๋ฐ๊พผ๋ค. ๊ณ ๋ฅธ ๊ฐ์ ๋ก์ปฌ ์ ์ฅ์ ๊ธ์ฒ๋ผ shared_preferences์ ์ ์ฅํด์, ๋ค์ ์ผ๋ ์ ์งํ๋ค.
MaterialApp(
locale: _locale, // null์ด๋ฉด ๊ธฐ๊ธฐ ์ธ์ด๋ฅผ ๋ฐ๋ฅธ๋ค
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
)์ฑ ์์ด์ฝ ๋ฐ๊พธ๊ธฐ: flutter_launcher_icons
๊ธฐ๋ณธ์ผ๋ก ๋ค์ด ์๋ Flutter ๋ก๊ณ ์์ด์ฝ์ ๋ด ์์ด์ฝ์ผ๋ก ๋ฐ๊ฟ ์ฐจ๋ก๋ค. ์๋๋ก์ด๋๋ ์ฌ๋ฌ ํฌ๊ธฐ์ ์ด๋ฏธ์ง๋ฅผ mipmap ํด๋๋ง๋ค, iOS๋ Assets.xcassets์ ํฌ๊ธฐ๋ณ๋ก ๋ฃ์ด์ผ ํด์ ์์ผ๋ก ํ๋ฉด ๋ฒ๊ฑฐ๋กญ๋ค. ์ด๋ฏธ์ง ํ ์ฅ์ผ๋ก ๋๋จธ์ง๋ฅผ ๋ง๋ค์ด์ฃผ๋ flutter_launcher_icons๋ฅผ ์ด๋ค.
flutter pub add dev:flutter_launcher_iconspubspec.yaml์ ์ค์ ์ ์ด๋ค.
flutter_launcher_icons:
android: true
ios: true
image_path: "assets/icon/icon.png"
adaptive_icon_background: "#FFFFFF"
adaptive_icon_foreground: "assets/icon/icon_foreground.png"
remove_alpha_ios: truedart run flutter_launcher_iconsimage_path: ์๋ณธ ์ด๋ฏธ์ง๋ค. 1024ร1024 ์ ๋์ ํฐ ์ ์ฌ๊ฐํ PNG๋ฅผ ์ด๋คadaptive_icon_*: ์๋๋ก์ด๋ 8 ์ด์์ด ๊ธฐ๊ธฐ๋ง๋ค ์์ด์ฝ ๋ชจ์(๋๊ทธ๋ผ๋ฏธ, ๋ฅ๊ทผ ์ฌ๊ฐํ)์ ๋ค๋ฅด๊ฒ ์๋ฅด๋ ๋ฐฉ์์ด๋ค. ๋ฐฐ๊ฒฝ๊ณผ ์์ ๊ทธ๋ฆผ์ ๋ฐ๋ก ์ค๋ค. ๊ทธ๋ฆผ์ ๊ฐ์ฅ์๋ฆฌ์ ๋ถ์ด์ง ์๊ณ ๊ฐ์ด๋ฐ์ ์ฌ์ ์๊ฒ ๋๋คremove_alpha_ios: iOS ์์ด์ฝ์ ํฌ๋ช ์์ญ์ด ์์ผ๋ฉด App Store๊ฐ ๋ฐ์ง ์๋๋ค. ํฌ๋ช ํ ๋ถ๋ถ์ ์ง์์ ๋ง๋ค์ด์ค๋ค
flavor๋ณ๋ก ์์ด์ฝ์ ๋๋ ๋๋ flutter_launcher_icons-<flavor>.yaml์ฒ๋ผ flavor ์ด๋ฆ์ ๋ถ์ธ ์ค์ ํ์ผ์ ์ด๋ค. ์์ด์ฝ์ ๋ฐ๊ฟจ๋๋ฐ ๊ธฐ๊ธฐ์ ๊ทธ๋๋ก๋ฉด ์ฑ์ ์ง์ฐ๊ณ ๋ค์ ์ค์นํด ๋ณธ๋ค.
์คํ๋์ ํ๋ฉด ๋ฐ๊พธ๊ธฐ: flutter_native_splash
์ฑ์ ์ผ๊ณ Flutter ํ๋ฉด์ด ๋จ๊ธฐ๊น์ง ์ ๊น ํฐ ํ๋ฉด์ด ๋ณด์ธ๋ค. ์ด ๊ตฌ๊ฐ์ ๋ณด์ด๋ ๊ฒ ์คํ๋์(์์) ํ๋ฉด์ด๋ค. Flutter ์์ ฏ์ด ์๋๋ผ ์ด์์ฒด์ ๊ฐ ์ฑ ์์ ๋ ๋ณด์ฌ์ฃผ๋ ๋ค์ดํฐ๋ธ ํ๋ฉด์ด๋ผ์, Dart ์ฝ๋๋ก๋ ๋ฐ๊ฟ ์ ์๊ณ flutter_native_splash๊ฐ ๋ค์ดํฐ๋ธ ์ค์ ์ ๋์ ๊ณ ์ณ์ค๋ค.
flutter pub add dev:flutter_native_splashflutter_native_splash:
color: "#FFFFFF"
image: assets/splash/logo.png
color_dark: "#000000"
image_dark: assets/splash/logo_dark.png
android_12:
color: "#FFFFFF"
image: assets/splash/logo_android12.pngdart run flutter_native_splash:createcolor,image: ๋ฐฐ๊ฒฝ์๊ณผ ๊ฐ์ด๋ฐ ๋ก๊ณ ๋คcolor_dark,image_dark: ๋คํฌ ๋ชจ๋์ผ ๋ ์ฐ๋ ๊ฐ์ด๋ค. ํ ๋ง ๊ธ์ ๋คํฌ ๋ชจ๋์ ๋ง์ถ๋คandroid_12: ์๋๋ก์ด๋ 12๋ถํฐ๋ ์์คํ ์ด ์คํ๋์๋ฅผ ์ง์ ๊ทธ๋ฆฐ๋ค. ์ฑ ์์ด์ฝ์ ์ ์์ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ์์ด๋ผ, ๋ฐ๋ก ์ค์ ํด์ผ ์๋ํ ๋ชจ์์ด ๋์จ๋ค
์คํ๋์๋ Flutter๊ฐ ์ค๋น๋๋ ์๊ฐ ์ฌ๋ผ์ง๋ค. ์๋ฒ์์ ์ฒซ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๋๊น์ง ๋ ๋ณด์ฌ์ฃผ๊ณ ์ถ๋ค๋ฉด FlutterNativeSplash.preserve(...)๋ก ๋ถ์ก์ ๋์๋ค๊ฐ ์ค๋น๊ฐ ๋๋๋ฉด FlutterNativeSplash.remove()๋ก ์ง์ด๋ค. ๋๋ฌด ์ค๋ ๋ถ์ก์ ๋๋ฉด ์ฌ์ฉ์๊ฐ ์ฑ์ด ๋ฉ์ถ ๊ฑธ๋ก ๋๋ผ๋ ์ฃผ์ํ๋ค.
Android, iOS์ ๋น๊ตํ๋ฉด
// Compose - ๋ฌธ์์ด ๋ฆฌ์์ค
Text(stringResource(R.string.hello, name))
// res/values/strings.xml, res/values-ko/strings.xml// SwiftUI - String Catalog
Text("hello \(name)")
// Localizable.xcstrings์ ์ธ์ด๋ณ ๋ฌธ๊ตฌ๋ฅผ ๋ชจ์๋ค- ๋ฌธ๊ตฌ ๋ชจ์ผ๋ ๊ณณ: Flutter
lib/l10n/app_*.arb/ Androidres/values-ko/strings.xml/ iOSLocalizable.xcstrings(String Catalog) - ๊บผ๋ด ์ฐ๊ธฐ: Flutter
AppLocalizations.of(context)!.hello(name)/ ComposestringResource(R.string.hello, name)/ SwiftUIText("hello \(name)") - ์คํ ๋ฐฉ์ด: Flutter์ Android๋ ์์ฑ๋ ์ด๋ฆ์ด๋ผ ์ปดํ์ผ ๋ ์กํ / SwiftUI ๋ฌธ์์ด ํค๋ String Catalog๊ฐ ํ์ธ
- ์์ด์ฝ: Flutter
flutter_launcher_icons/ Android Studio Image Asset / Xcode Assets - ์คํ๋์: Flutter
flutter_native_splash/ Android 12 ์ด์ SplashScreen API / iOS LaunchScreen
Android์ strings.xml๊ณผ ARB๋ ๊ฐ์ ๋ฐ์์ด๋ค. ๋ค๋ง Flutter์ ARB๋ ์ธ ํ๋ซํผ์ด ํ ํ์ผ์ ๊ณต์ ํ๋ค๋ ๊ฒ ๊ฐ์ ์ด๋ค. ์์ด์ฝ๊ณผ ์คํ๋์๋ ๊ฒฐ๊ตญ ๊ฐ ํ๋ซํผ์ ๋ค์ดํฐ๋ธ ์ค์ ์ ๋ฐ๊พธ๋ ์ผ์ด๋ผ, ๋๊ตฌ๊ฐ ๋์ ํด์ค ๋ฟ ์์์ ํ๋ ์ผ์ ๊ฐ๋ค.
์ ๋ฆฌ
- ๋ฌธ๊ตฌ๋ฅผ ์ฝ๋์ ์ง์ ์ ๊ณ ๋ถ๊ธฐํ๋ฉด ์ธ์ด๋ฅผ ๋๋ฆด ๋๋ง๋ค ๋ชจ๋ ๊ณณ์ ๊ณ ์ณ์ผ ํ๋ฏ๋ก, ARB ํ์ผ์ ์ธ์ด๋ณ๋ก ๋ชจ์๋ค
flutter_localizations,intl์ ์ค์นํ๊ณflutter: generate: true,l10n.yaml,lib/l10n/app_en.arb๋ฅผ ๊ฐ์ถ ๋คflutter gen-l10n์ผ๋กAppLocalizations๋ฅผ ๋ง๋ ๋คMaterialApp์localizationsDelegates์supportedLocales๋ฅผ ๋ฃ๊ณ ,AppLocalizations.of(context)!.ํค()๋ก ๊บผ๋ด์ ํค ์คํ๋ฅผ ์ปดํ์ผ ๋ ์ก๋๋คAppLocalizations๋ฅผ ๋ชป ์ฐพ์ผ๋ฉดgenerate: true,l10n.yaml, ARB ํด๋, ์์ฑ ๋ช ๋ น์ ํ์ธํ๊ณ , ARB๋ฅผ ๊ณ ์น๋ฉด ๋ค์ ์์ฑํ๋ค. iOS๋CFBundleLocalizations๋ ์ฑ๊ธด๋ค- ๋ ์ง์ ๊ธ์ก์
intl์DateFormat,NumberFormat์ผ๋ก ์ธ์ด์ ๋ง์ถ๋ค - ์์ด์ฝ์
flutter_launcher_icons๋ก ์ด๋ฏธ์ง ํ ์ฅ์์ ๋ง๋ค๊ณ iOS๋ ํฌ๋ช ์์ญ์ ์์ ๋ฉฐ, ์คํ๋์๋flutter_native_splash๋ก ๋ฐ๊พผ๋ค. ์คํ๋์๋ Flutter ์์ ฏ์ด ์๋๋ผ ๋ค์ดํฐ๋ธ ํ๋ฉด์ด๋ค
๋ค์ ๊ธ์์๋ ๊ฐ์ ์ฑ์ ๊ฐ๋ฐ์ฉ๊ณผ ์ด์์ฉ์ผ๋ก ๋๋ ์ ๋ง๋๋ flavor๋ฅผ ๋ค๋ค๋ณด๊ฒ ๋ค.
