# ทำแอปหลายภาษาง่ายติ้ดดดเดียว

#Dev#Flutter#Flutter 2#Localization#Vscode#I18n Ally

สำหรับใครที่ต้องการจะทำแอปให้รองรับหลายภาษา สามารถอ่านบทความนี้แล้วทำตามกันได้เลยครับ จริงๆเรื่องนี้ก็เคยเขียนมาแล้วที่บทความ นี้

โดยตัวที่ผมจะใช้นั้นมีชื่อว่า easy_localization (opens new window) ซึ่งตอนนี้เป็น Version 3.0.0 แล้ว มีการปรับเปลี่ยนการใช้งานให้ง่ายขึ้นกว่าเดิมเข้าไปอีกมากมาย

Header

# สร้างโปรเจค

สร้างโปรเจคเลยครับแล้วก็นี่คือโค้ดเริ่มต้นของผม และเมื่อเราเปิดดูไฟล์ lib/main.dart เราก็จะเห็น (ก็โค้ดเริ่มต้นนั่นล่ะ ข้ามๆไปได้)

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

Note

ซึ่งตอนที่เขียนบทความนี้ Flutter พึ่งเปิดตัว Null-Safety ในภาษา Dart เราก็จะเริ่มใช้กันเลย

# เริ่มใช้ Null-Safety

เราสามารถใช้ Migrate Tools ที่ทาง Dart ทำมาให้ได้เลยโดยใช้คำสั่ง

dart migrate --apply-changes

# เริ่ม Setup กันเลยครับ

# เพิ่ม Dependencies

ทำการติดตั้ง easy_localization (opens new window) โดยเราจะต้องเพิ่ม dependencies ที่ไฟล์ pubspec.yaml ดังนี้










 


...
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2

  easy_localization: ^3.0.0
...

ต่อไปเราจะทำการเพิ่มโฟลเดอร์ assets หรือไฟล์คำแปลของเรา ให้ตัว Flutter รู้ว่าจะทำการใช้ไฟล์ไหนบ้าง โดยการเลื่อนไปข้างล่างของไฟล์ pubspec.yaml




 
 


...
flutter:
  ...
  assets:
    - assets/lang/
...

จากนั้นก็รัน

flutter pub get

# หน้าแอปและส่วนที่เราจะทำการแปลภาษา

Title
หน้าตาแอปเมื่อเปิดมาครั้งแรก

# เพิ่มไฟล์ภาษาต่างๆ

จากนั้นให้ทำการเพิ่มไฟล์ภาษาในรูปแบบ assets/lang/{langCode}-{countryCode}.json เช่น assets/lang/en-US.json
หรือจะไม่ใส่ country code ก็ได้ assets/lang/{langCode}.json เช่น assets/lang/en.json

ไฟล์ภาษาอังกฤษ assets/lang/en.json

{
  "app": {
    "title": "Multi locale clicker",
    "changeLang": "เปลี่ยนภาษา",
    "description": "You have pushed the button this many times",
    "increment": "Increment",
    "counter": {
      "zero": "{} times",
      "one": "{} time",
      "two": "{} times",
      "other": "{} times"
    }
  }
}

ไฟล์ภาษาไทย assets/lang/th.json

{
  "app": {
    "title": "Multi locale clicker",
    "changeLang": "Change language",
    "description": "คลิกสิคลิกหลายๆที",
    "increment": "เพิ่ม",
    "counter": {
      "zero": "{} ครั้ง",
      "one": "{} ครั้ง",
      "two": "{} ครั้ง",
      "other": "{} ครั้ง"
    }
  }
}

Note

app.changeLang จะใช้กับปุ่มเปลี่ยนภาษา
counter ตั้งใจทำไว้เพื่อรองรับ Plural ในภาษาอังกฤษ

# ตั้งค่า easy_localization ลงในแอป

หลายขั้นตอนค่อยๆทำไปครับ โดยเราจะทำการแก้ไฟล์ 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('th')],
      path: 'assets/lang',
      fallbackLocale: Locale('th'),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
...

สิ่งที่เราทำก็คือเพิ่ม Widget EasyLocalization มาก่อน Widget MyApp
โดยเจ้า EasyLocalization จะเป็นตัวหลักในการตั้งค่า Locale ต่างๆ

ทีนี้ก็ทำการเพิ่ม Localization ให้กับ MaterialApp (หรือ CupertinoApp)






 
 
 










...
class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
...

EasyLocalization จะเป็นตัวที่กำหนด context.localizationDelegates context.supportedLocales context.locale ให้เราเอามาใช้ได้

ก็เป็นอันเรียบร้อย ทีนี้มาเริ่มแปลภาษากันครับ โดยการใช้คำสั่ง tr('...'), '...'.tr() หรือ Text('...').tr()




 
 
 
 
 
 
 



...
return MaterialApp(
  ...
  home: Builder(
    builder: (context) {
      return MyHomePage(
        title: tr('app.title'),
      );
    },
  ),
);
...

Note

เพราะฟังก์ชั่น Localization ถูกสร้างที่ Widget MaterialApp ถ้าอยากจะเรียกใช้การแปลภาษาที่ MaterialApp ผมต้องซ้อนผ่าน Widget Builder ก่อน

แปลในส่วนของหน้าหลักกันต่อ










 
 
 
 
 





 





...
return Scaffold(
  appBar: AppBar(
    title: Text(widget.title!),
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('app.description').tr(),
        Text(
          'counter',
          style: Theme.of(context).textTheme.headline4,
        ).plural(_counter),
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    onPressed: _incrementCounter,
    tooltip: tr('Increment'),
    child: Icon(Icons.add),
  ),
);
...

เมื่อรันแอปขึ้นมาก็จะเจอกับแอปในรูปแบบนี้ครับ

Title
หน้าตาแอปเมื่อเปิดแล้วลองกดปุ่มดู

# เขียนส่วนตั้งค่าเปลี่ยนภาษา

เนื่องด้วย Library ตัวนี้เขียนไว้ให้จำภาษาแรกไว้ (คือเปลี่ยนภาษาเครื่องเป็นภาษาอื่นก็จะไม่มีการเปลี่ยนตาม) ถ้าเราต้องการเปลี่ยนภาษาเราอาจจะต้องเขียนส่วนของการตั้งค่าเพิ่มเอง






 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 




...
return Scaffold(
  appBar: AppBar(
    title: Text(widget.title),
    actions: <Widget>[
      Padding(
        padding: const EdgeInsets.symmetric(horizontal: 10.0),
        child: TextButton(
          child: Text(
            tr('app.changeLang'),
            style: TextStyle(color: Colors.white),
          ),
          onPressed: () => setState(() {
            if (context.locale.languageCode == 'en') {
              context.setLocale(Locale('th'));
            } else {
              context.setLocale(Locale('en'));
            }
          }),
        ),
      )
    ],
  ),
...

ทีนี้ลองกดปุ่ม เปลี่ยนภาษา ดู

Title
เย่ เปลี่ยนภาษาได้แล้ว

Note

แม้จะปิดแอปเปิดใหม่ ภาษาที่เราเปลี่ยนก็จะยังคงอยู่
เพราะ Library นี้มีการบันทึกค่าภาษาลงใน Shared Preferences

# Bonus

มี Plugins แนะนำสำหรับใช้ในการจัดการ Localization บน VSCode ที่ชื่อ i18n Ally (opens new window)

# ตั้งค่าเพื่อใช้กับ Easy Localization

  1. กด Ctrl+Shift+P (Command+Shift+P บน MacOS) และเลือก i18n Ally: Auto-detect locales path
  2. เพิ่มไฟล์ .vscode/i18n-ally-custom-framework.yml
# .vscode/i18n-ally-custom-framework.yml

# An array of strings which contain Language Ids defined by VS Code
# You can check avaliable language ids here: https://code.visualstudio.com/docs/languages/overview#_language-id
languageIds:
  - dart

# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
# You should unescape RegEx strings in order to fit in the YAML file
# To help with this, you can use https://www.freeformatter.com/json-escape.html
usageMatchRegex:
  # The following example shows how to detect `t("your.i18n.keys")`
  # the `{key}` will be placed by a proper keypath matching regex,
  # you can ignore it and use your own matching rules as well
  - "Text\\(\\s*['\"`]({key})['\"`][\\s\\w:,\\(\\)]*\\)\\.tr\\("
  - "['\"`]({key})['\"`]\\.tr\\("
  - "[^\\w\\d]tr\\(['\"`]({key})['\"`]"


# An array of strings containing refactor templates.
# The "$1" will be replaced by the keypath specified.
# Optional: uncomment the following two lines to use

# refactorTemplates:
#  - i18n.get("$1")


# If set to true, only enables this custom framework (will disable all built-in frameworks)
monopoly: true

ทีนี้ VSCode ก็จะช่วยเราดูได้แล้วว่ามีอะไรที่เราแปลไม่ครบบ้าง

Title

ดาวน์โหลด Source Code ที่นี่ (opens new window)


อัปเดตเมื่อ: 7 ชั่วโมงที่แล้ว