Language Helper
Multi-language app tool with an efficient generator and a custom GPT-4 translator for easy localization.
Features
-
Easy to control the language translations in your application. Automatically uses the current device locale upon first open, if possible.
-
You can completely control the translated text with
LanguageConditions
. -
Supports analyzing which text is missing in a specific language or is in your app but not in your language data, and vice versa.
-
Supports extracting the needed text for translation from all
.dart
files in your project with a single command (Not usingbuild_runner
nor custom parser so it very fast and reliable). -
A
Language Helper Translator
on Chat GPT-4 that make it easier to translate the language data to a destination language.
Contents
- Set Up: Only this step is required while developing
- Generator Flow Usage
- Manual Flow Usage
- Using
LanguageBuilder
To Update TheString
s - Control The Translation
- Advanced Language Helper Generator
- Language Data Serialization
- Language Helper Translator (A Custom Chat GPT-4)
- Additional Information
- Contributions
Set Up
While developing, we just need to finish the Set Up steps. All other steps can be done when the app is ready to implement the localizations.
Add The language_helper To The Project
flutter pub add language_helper
Add An Empty LanguageHelper While Developing
final languageHelper = LanguageHelper.instance;
main() async {
WidgetsFlutterBinding.ensureInitialized();
await languageHelper.initial(data: []);
runApp(const MyApp());
}
Add .tr
Or .trP
To All Needed String
s
Normal translation
Text('Translate this text'.tr)
Translate with parameters
Text('Hello @{name}'.trP({'name': name}))
Plural (Read Manual Flow Usage to know how to use it)
Text('We have @{number} dollar'.trP({'number': number}))
iOS configuration
Add the supported localizations to the Infi.plist
. For instance with en
and vi
supported:
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>vi</string>
</array>
Generator Flow Usage
Dart Map
Generate:
dart run language_helper:generate
The data will be generated in this path by default:
|-- .lib
| |--- resources
| | |--- language_helper
| | | |--- language_data.dart
| | | |--- languages
| | | | |--- _generated.dart ; This file will be overwritten when re-generating
Implement to your project:
final languageHelper = LanguageHelper.instance;
final languageDataProvider = LanguageDataProvider.data(languageData);
main() async {
await languageHelper.initial(
data: [languageDataProvider],
);
runApp(const MyApp());
}
JSON
When using JSON, you can store your translation data in assets
or on network
dart run language_helper:generate --json
The data will be generated in this path by default:
|-- assets
| |--- resources
| | |--- language_helper
| | | |--- codes.json
| | | |--- languages
| | | | |--- _generated.json ; This file will be overwritten when re-generating
Implement to your project
Define the language data:
final languageHelper = LanguageHelper.instance;
// Network
final languageDataProvider = LanguageDataProvider.network('https://example.com/resources');
// Assets
final languageDataProvider = LanguageDataProvider.asset('assets/resources');
Add to the LanguageHelper
instance:
final languageHelper = LanguageHelper.instance;
main() async {
await languageHelper.initial(
data: [languageDataProvider],
);
runApp(const MyApp());
}
Combine all of them to improve the translation:
main() async {
await languageHelper.initial(
data: [
LanguageDataProvider.network('https://example.com/resources'),
LanguageDataProvider.asset('assets/resources'),
LanguageDataProvider.data(languageData),
],
);
runApp(const MyApp());
}
The package will get the translation data in order from top to bottom.
Manual Flow Usage
Create The Translations
Dart Map:
final en = {
'Translate this text': 'Translate this text',
'Hello @{name}': 'Hello @{name}',
'We have @{number} dollar': LanguageConditions(
param: 'number',
conditions: {
'0': 'We have zero dollar',
'1': 'We have one dollar',
// Default value.
'_': 'We have @{number} dollars',
}
),
};
const vi = {
'Translate this text': 'Dịch chữ này',
'Hello @{name}': 'Xin chào @{name}',
'We have @{number} dollar': 'Chúng ta có @{number} đô-la',
};
LanguageData languageData = {
LanguageCodes.en: en,
LanguageCodes.vi: vi,
};
final languageDataProvider = LanguageDataProvider.data(languageData);
With LanguageConditions
, you can completely control which text is returned according to the parameters' conditions. You can use 'default'
or '_'
to set the default value for the condition.
JSON:
assets/resources/language_helper/codes.json
: Contains all language codes
["en", "vi"]
assets/resources/language_helper/languages/en.json
:
{
"Translate this text": "Translate this text",
"Hello @{name}": "Hello @{name}",
"We have @{number} dollar": {
"param": "number",
"conditions": {
"0": "We have zero dollar",
"1": "We have one dollar",
// Default value.
"_": "We have @{number} dollars",
}
}
}
assets/resources/language_helper/languages/vi.json
:
{
"Translate this text": "Dịch chữ này",
"Hello @{name}": "Xin chào @{name}",
"We have @{number} dollar": "Chúng ta có @{number} đô-la",
}
Remember to add those files to the pubspec.yaml
:
flutter:
assets:
- assets/resources/language_helper/codes.json
- assets/resources/language_helper/languages/
final languageDataProvider = LanguageDataProvider.asset('assets/resources');
Add To The Project
final languageHelper = LanguageHelper.instance;
main() async {
await languageHelper.initial(
data: [languageDataProvider],
);
runApp(const MyApp());
}
Using LanguageBuilder
To Update The String
s
In the MaterialApp
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return LanguageBuilder(
builder: (context) {
return MaterialApp(
localizationsDelegates: languageHelper.delegates,
supportedLocales: languageHelper.locales,
locale: languageHelper.locale,
home: const HomePage(),
);
}
);
}
}
In your Widget
s
Using LanguageBuilder
:
LanguageBuilder(
builder: (context) {
return Scaffold(
body: Column(
children: [
Text('Hello @{name}'.tr),
Text('We have @{number} dollar'.tr),
],
),
);
},
),
Using Tr
(A short version of LanguageBuilder
):
Tr((_) => Text('Hello @{name}'.tr)),
Control The Translation
Change The Language
languageHelper.change(LanguageCodes.vi);
Add A New Language Data
languageHelper.addData(LanguageDataProvider.data(newLanguageData));
languageHelper.addDataOverrides(LanguageDataProvider.data(newLanguageDataOverrides));
The addData
and addDataOverrides
have activate
parameter which automaticaly rebuild all needed LanguageBuilder
, so notice that you may get the setState
issue because of the rebuilding of the LanguageBuilder
when it's still building. If the error occurs, you may need to set it to false
and activate the new data yourself by using reload
method.
Get The List Of Supported Language Code
// List of [LanguageCodes] from both of the [data] and [dataOverrides] without duplicated
final codes = languageHelper.codes;
// List of [LanguageCodes] from the [dataOverrides]
final codesOverrides = languageHelper.codesOverrides;
Listen To The Language Changing State
Beside the onChanged
callback, you can listen to the language changed events by using stream
:
final sub = languageHelper.stream.listen((code) => print(code));
Note: Remember to sub.cancel()
when it's not in use to avoid memory leaks.
Analyze The Translation
Currently works properly with LanguageDataProvider.data
method
languageHelper.analyze();
This function will automatically be called in initial
when isDebug
is true
.
Advanced Language Helper Generator
Modify The Input Path
Add --path
option to your command:
dart run language_helper:generate --path=./lib
Modify The Output Path
Add --output
option to your command:
dart run language_helper:generate --output=./lib/resources
Convert From LanguageData
to JSON
- Create a
bin
folder in the same level with yourlib
. - Create a
export_json.dart
file in yourbin
. - Add this code to your
export_json.dart
:
void main() {
test('', () {
languageData.exportJson('./assets/resources');
});
}
- Add the missing
import
s. - Run
flutter test ./bin/export_json.dart
. - The JSON will be generated in this path:
assets
| |- language_helper
| | |- codes.json
| | | |- languages
| | | | |- en.json
| | | | |- vi.json
| | | | |- ...
Language Data Serialization
Convert LanguageData
to JSON:
final json = data.toJson();
Convert JSON to LanguageData
:
final data = LanguageDataSerializer.fromJson(json);
Language Helper Translator (A Custom Chat GPT-4)
- Assume that here is our language data:
final en = {
'Hello @{name}': 'Hello @{name}',
'We have @{number} dollar': LanguageConditions(
param: 'number',
conditions: {
'0': 'We have zero dollar',
'1': 'We have one dollar',
// Default value.
'_': 'We have @{number} dollars',
}
),
};
- Go to Language Helper Translator. You should open a New Chat a few times to let the AI read the instructions carefully to improve the translation (just my personal experience).
- Use this template to translate the data. Be sure to replace
[]
with the appropriate infomation:
This is the translation of the [app/game] that [purpose of the app/game to help the AI understand the context]. Translate it into [destination language]:
final en = {
'Hello @{name}': 'Hello @{name}',
'We have @{number} dollar': LanguageConditions(
param: 'number',
conditions: {
'0': 'We have zero dollar',
'1': 'We have one dollar',
// Default value.
'_': 'We have @{number} dollars',
}
),
};
- The GPT will keeps all keys and comments in their original text, positions them exactly as they appear in the source, keeps the @{param} and @param in their appropriate places during the translation.
Additional Information
-
The app will try to use the
Devicelocale
to set theinitialCode
if it is not set. If theDevicelocale
is unavailable, it will use the first language indata
instead. -
No matter how many
LanguageBuilder
that you use, the plugin only rebuilds the outest (the root) widget ofLanguageBuilder
, so it significantly improves performance. If you want to force rebuild some Widget, you can set theforceRebuild
parameter in theLanguageBuilder
totrue
. -
The
LanguageCodes
contains all the common languages with additional information like name in English (englishName) and name in native language (nativeName). -
The
@{param}
works in all cases (We should use this way to avoid issues when translating withLanguage Helper Translator
). The@param
only work if the text ends with a white space, end of line, or end with a new line. -
The
addData
andaddDataOverrides
haveactivate
parameter which automaticaly rebuild all neededLanguageBuilder
, so notice that you may get thesetState
issue because of the rebuilding of theLanguageBuilder
when it's still building. If the error occurs, you may need to set it tofalse
and activate the new data yourself by usingreload
method. -
The
assets
data is preferred betweenassets
andnetwork
because we still haven't a way to cache it. -
Use the
isInitialized
(bool) andensureInitialized
(Future
Contributions
As the project is currently in its early stages, it may contain bugs or other issues. Should you experience any problems, we kindly ask that you file an issue to let us know. Additionally, we welcome contributions in the form of pull requests (PRs) to help enhance the project.