Flutter 1.22 Internationalization with variable as key - flutter

I implemented the new (official) localization for Flutter (https://pascalw.me/blog/2020/10/02/flutter-1.22-internationalization.html) and everything is working fine, except that I don't know how to get the translation for a variable key.
The translation is in the ARB file, but how can I access it?
Normally I access translations using Translations.of(context).formsBack, but now I would like to get the translation of value["labels"]["label"].
Something like Translations.of(context).(value["labels"]["label"]) does not work of course.

I don't think this is possible with gen_l10n. The code that is generated by gen_l10n looks like this (somewhat abbreviated):
/// The translations for English (`en`).
class TranslationsEn extends Translations {
TranslationsEn([String locale = 'en']) : super(locale);
#override
String get confirmDialogBtnOk => 'Yes';
#override
String get confirmDialogBtnCancel => 'No';
}
As you can see it doesn't generate any code to perform a dynamic lookup.
For most cases code generation like this is a nice advantage since you get auto completion and type safety, but it does mean it's more difficult to accommodate these kinds of dynamic use cases.
The only thing you can do is manually write a lookup table, or choose another i18n solution that does support dynamic lookups.
A lookup table could look something like this. Just make sure you always pass in the current build context, so the l10n code can lookup the current locale.
class DynamicTranslations {
String get(BuildContext context, String messageId) {
switch(messageId) {
case 'confirmDialogBtnOk':
return Translations.of(context).confirmDialogBtnOk;
case 'confirmDialogBtnCancel':
return Translations.of(context).confirmDialogBtnCancel;
default:
throw Exception('Unknown message: $messageId');
}
}
}

To provide an example for https://stackoverflow.com/users/5638943/kristi-jorgji 's answer (which works fine):
app_en.arb ->
{
"languages": "{\"en\": \"English\", \"ro\": \"Romanian\"}"
}
localization_controller.dart ->
String getLocaleName(BuildContext ctx, String languageCode) {
return jsonDecode(AppLocalizations.of(ctx)!.languages)[languageCode];
}
getLocaleName(context, 'ro') -> "Romanian"

You can store a key in translation as json string.
Then you read it, parse it to Map<string,string> and access dynamically what you need.
Been using this approach with great success

Related

how can I compound getters in dart to avoid duplication

I have lots of getters for localizations (they are coming from the json file for example) such as:
String get start {
return localizedValues[locale.languageCode]!['start']!;
}
String get hours {
return localizedValues[locale.languageCode]!['hours']!;
}
String get minutes {
return localizedValues[locale.languageCode]!['minutes']!;
}
String get seconds {
return localizedValues[locale.languageCode]!['seconds']!;
}
So I want to compound them since they use some common codes (I tried to create final someWord = commonCode; but It did not work, so I wanted to ask)
Now, If I want to reach them, I use myClass.of(context).start for example. So, in the end, I will reach using the same way but in the class which is above, I will not do any duplication If you help me. So, I need your help to avoid duplication.
I would recommend using a data class to hold localisation data instead of just a Map, or using a package such as i10n to do the translations for you.
There are a couple of ways you can shorten this code. For starters, you can extract localizedValues[locale.languageCode]! to its own getter, like this:
Map<String, String> get localeValues => localizedValues[locale.languageCode]!;
Then all your methods become a bit shorter:
String get seconds {
return localeValues['seconds']!;
}
Additionally, you can shorten your getters by removing the function body and using an arrow function instead, like I did above for the localeValues:
String get seconds => localeValues['seconds']!;
Do this for all your getters, and your code is now shorter.
You can use this method, and shorten the code:
String getTime(String languageCode, String time) {
return localizedValues[languageCode]![time]!;
}
getTime("1529", "seconds");
getTime("859", "mitutes");
getTime("9632", "hours");

How to enforce a new property has a value set at compile time in dart?

I am new to dart/flutter and I am adding internationalization on my app.
As flutter don't define a proper way to implement internationalization (according to the video I watched), devs are free to do it in any way they think its better.
The video showed an implementation using json files. So on "lang" folder I have my json files with all strings and their respective translations.
Now imagine this is a large file with lots of entries. And I have to make a big change on my app, removing some strings, adding others and adding a new language. This is very error prone as I may forget to add some key-value entry in any of the json files. It happened sometimes and the error is thrown at runtime.
In java there is a way to enforce the dev don't forget to add the same values in all variations of the same property at compile time. Maybe it's not the best way to achieve this, but one way is using an enum like in the image below.
Now if I want to add a new language, I just add a new parameter in the enum constructor and there is no way to forget adding a translation because the code would not even compile.
To fix the error I just have to add the value on the Strings (I forgot the "es" case here):
And then I can use the values doing something like this (the code to find the locale using the context could even be moved to inside the .value() method):
So is there a good way to achieve this in dart/flutter? To enforce all the languages have all the translated texts at compile time?
My method looks like this:
First, define the interface, and its implementations:
abstract class StringsInterface {
String get greet;
}
class EnglishStrings implements StringsInterface {
String get greet => "Hello";
}
class SpanishStrings implements StringsInterface {
String get greet => "Hola";
}
Next, define a class to store an instance of the current locale, as well as configure which class to use, for which language code:
class Strings {
static StringsInterface current = EnglishStrings();
static late Locale _locale;
static Locale get locale => _locale;
static set locale(Locale locale) {
_locale = locale;
current = stringsForLocale(locale);
}
static StringsInterface stringsForLocale(Locale locale) {
switch(locale.languageCode) {
// Here is where you define which class gets used for which language code
case 'es': return SpanishStrings();
default: return EnglishStrings();
}
}
}
Set the locale for the Strings class at runtime:
void main() async {
/// load from your own future
/// (e.g. pull from a persisted value stored on device in SharedPreferences/UserDefaults)
Strings.locale = Locale(await Future<String>.value('en'));
runApp(const MyApp());
}
Finally, use like this:
Text(Strings.current.greet)
As of dart 2.17 you can now use enhanced enums with members:
enum Strings {
welcome(en: 'Welcome', pt: 'Bem-vindo'),
goodbye(en: 'Good bye', pt: 'Adeus');
final String en;
final String pt;
const Strings({
required this.en,
required this.pt,
});
String value(String lang) {
switch (lang) {
case 'pt':
return pt;
case 'en':
default:
return en;
}
}
}

A way to read a String as dart code inside flutter?

I want to build a method to dynamically save attributes on a specific object
given the attribute name and the value to save I call the "save()" function to update the global targetObj
var targetObj = targetClass();
save(String attribute, String value){
targetObj.attribute = value;
print(targetObj.attribute);
}
But I'm getting the following error:
Class 'targetClass' has no instance setter 'attribute='.
Receiver: Instance of 'targetClass'
Tried calling: attribute="Foo"
The only thing that I can think of is that "attribute" due to being type String results in an error.
That lead me to think if there is a way to read a String as code, something like eval for php.
As #Randal mentioned, you cannot create class..method at runtime. Still, you can try something like this.
A certain class
class Foo {
dynamic bar1;
dynamic bar2;
// ...
}
Your save method
save(Foo fooObject, String attribute, dynamic value) {
if ("bar1" == attribute) fooObject.bar1 = value;
else if ("bar2" == attribute) fooObject.bar2 == value;
// ...
}
Dart (and thus flutter) does not have a way to compile and execute code at runtime (other than dart:mirrors, which is deprecated). You can build additional code that derives from other code using the various builder mechanisms, although it can be rather complicated to implement (and use!).

Flutter use class for JSON

I try to learn to flutter but notions are quite difficult for me.
I was working with local json files which I load at the start and then do calculations with the values ​​inside.
But I see everywhere that everyone uses class to parse JSON.
Classes are a fairly complex concept for me. I find it more complicated to handle.
In my example if I get a particular user and I want to add a KEY / VALUE to it, I can't do it if I go through a class. How to do ?
Is it useful to use class if I just load json files and retrieve values ​​inside to do statistics?
My homeController.dart
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
import 'package:myapp/widgets/users.dart';
Future<Map<String, dynamic>> loadJson() async {
final myJson = await rootBundle.loadString('assets/jsons/myjson.json');
return {
'myJson': myJson,
};
}
class HomeController extends StatefulWidget {
HomeController({Key key, this.title}) : super(key: key);
final String title;
#override
_HomeControllerState createState() => _HomeControllerState();
}
class _HomeControllerState extends State<HomeController> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: loadJson(),
builder: (context, snapshot) {
if(snapshot.hasError) print(snapshot.error);
if (!snapshot.hasData) {
return LinearProgressIndicator();
}
print('WITH CLASS =====================================');
var myJson1 = jsonDecode(snapshot.data['myJson'])['user'] as List;
List<User> myList1 = myJson1.map((e) => User.fromJson(e)).toList();
List selectedInList1 = myList1.where((e) => e.arg2>= 250).toList();
User chooseObject1 = selectedInList1.first;
chooseObject1.putIfAbsent('newKey', () => 'test'); // ???????????????????????
print(chooseObject1);
print('WITHOUT CLASS =====================================');
var myJson = json.decode(snapshot.data['myJson']) ;
List myList = myJson['user'];
List selectedInList = myList.where((e) => e['arg2'] >= 250).toList();
Map chooseObject = selectedInList.first;
chooseObject['newKey'] = "test";
print(chooseObject);
// Some code ...
My json
{
"user" : [
{
"arg1" : 1,
"arg2" : 200
},
{
"arg1" : 1,
"arg2" : 250
},
{
"arg1" : 1,
"arg2" : 300
}
]
}
My User Class
class User{
final int arg1;
final int arg2;
User(
this.arg1,
this.arg2
);
factory User.fromJson(dynamic json){
return User(
json['arg1'] as int,
json['arg2'] as int
);
}
#override
String toString() {
return '{ ${this.arg1}, ${this.arg2} }';
}
}
Studying a framework, library, engine, a new programming language, other technology (any abstraction) without knowing something that is required to know to be able to use it is more difficult and longer (a priori) task. It's like studying physics without some math knowledge. Of course, it doesn't mean that it's impossible. More than that many people are learning like that all the way. It's like not learning from bottom to top, but from top to bottom. It means that you want to make something, and to do it, you don't learn everything and then do it, but trying to do something, then learn the next things that a required to go closer to your goal. And everything is OK with that method. Everyone decides which method fits him better.
Everyone and everywhere use classes because classes in OOP languages (and Dart is an OOP language) are one of the base blocks using which we are creating our programs. More than that, I should mention, that all your program is written with classes. Classes are everywhere in Dart, practically everything is a class in Dart (if not everything literally).
You've said that you can't add a key/value to your class. You can't because you've not added such a behavior to your User class. Map is also just a class (that has been written for you by Dart creators). And it doesn't mean that you could not write something like that. You can just add a method (or operator overloading) to accomplish this task. It could be like that:
class User{
// ...
void add(String key, dynamic value) {
// ... implementation
}
}
And then you could do just the same.
Another open question is why do you really need to add a new key/value pair to your User? What problem that ability will help you to solve? Maybe you just declare this field in the User class? Or maybe you need to define another class? I couldn't answer this question, because I don't know what are you going to achieve.
So, I suppose the real question is: why should everyone prefer parse JSON data into custom classes instead of using a predefined Map class? There are many reasons to do that. First of all: it is to be able to reason about (and not only for humans but for a compilers either). What does Map class represents? It represents a collection of key and value pairs. No more no less. So there is no guarantee of what a particular collection contains? It could be empty, or it could contain something else or whatever. And working with such a collection is more difficult and error-prone (you are to remember everything by yourself, and the compiler will not help you). In our case, we could say that a map is raw data, i.e. a collection of unknown raw data. We do not really know what it is exactly until we'll check it. But when we define a class for our data we have not just a collection of raw data but a collection of User (a collection with known keys (data) and behavior (methods)). No more no less, with some guarantees. Like every object in that collection has a type of User. Every object has all fields that are declared in the User class. All of those fields have a specified type. It is very convenient. And now we not only know with what data we are working but the compiler either. And it will help us all the time. It will show type errors, it will generate more reliable and fast programs. And many other advantages. For example, when you access a property like that: e['arg2'] a compiler doesn't know whether the e contains such a key or not. But when you access to property like that: e.arg2 than the compiler 100% sure that such a key exists. Then, for example, let's imagine that we need to change a key name for some reason. In the first example, we can change it in one place and we can forget (easily) in every other place. So the program wouldn't work well in such a case. If we change a key name in the User class we wouldn't able to compile a program until we change every line of code with the old key name. It's also a huge difference. Of course, when you are writing a small program like that it's not a big deal to change it and to remember everything. But software complexity grows extremely fast and even in a medium program, it becomes a real problem. 20% of the time we are writing a program and 80% we are debugging it and fixing bugs. Classes allow us to greatly reduce the number of things that we should think about ourselves.
Of course, there are many other reasons (and it's difficult to put them all down in one answer), it's just a simple example to answer why. You can continue to write programs in a way that is convenient for you. It's OK. Some day, with some experience you will grasp the advantages of classes either. If you want to have some deeper know you are to read some OOP book and have some practice with it.

What is the point of GlobalMaterialLocalizations and flutter_localizations?

The Flutter article for internationalization states the following:
To add support for other languages, an application must specify additional MaterialApp properties, and include a separate package called flutter_localizations.
However, I do not understand what the point of flutter_localizations is and why I "must [...] include" it in my app if I want to add support for other languages.
If I take a look at GlobalMaterialLocalizations, which is what flutter_localizations apparently adds (along with the Widgets and Cupertino versions), I can only find a bunch of seemengly random strings I have never seen translated into a bunch of languages.
I have never seen these strings in my Flutter app.
Do I really need to include flutter_localizations if I want to internationalize my app, i.e. are these strings used anywhere by default?
Actually no.
It depends how you want to implement you internationalization.
You can get current local by:
/// Because of flutter issue
/// https://github.com/flutter/flutter/issues/38323
/// So return `locals[1]` instead of `window.locale`;
var locals = window.locales;
var local = (locals.length > 1) ? locals[1] : window.locale;
Benefit of current flutter_localizations
I think the benefit of this lib is that it also handle the text direction stuff in
The other way
If you only need to handle the Strings' translation, no need to handle text direction(ltr(left to right) or rtl(right to left)):
You handle your strings by the value returned above:
abstract class Strings {
String hello;
factory Strings() // => instance;
{
var locals = window.locales;
var local = (locals.length > 1) ? locals[1] : window.locale;
if (local != null) {
// log('Strings.load(${local.languageCode})');
if (local.languageCode == 'zh') {
return _StringsZH.instance;
}
}
return _StringsEN.instance;
}
}
class _StringsEN implements Strings {
String hello = 'Hello 2019';
_StringsEN._();
static _StringsEN instance = _StringsEN._();
}
class _StringsZH implements Strings {
String hello = '你好 2019';
_StringsZH._();
static _StringsZH instance = _StringsZH._();
}
And use it like
Strings().hello;