Jesway / flutter_translate

Flutter Translate is a fully featured localization / internationalization (i18n) library for Flutter.
MIT License
401 stars 118 forks source link

V5.0 support list and maps #120

Closed Judimax closed 3 months ago

Judimax commented 5 months ago

Complete with Changelog Test Cases (of the whole library!) and docs

[UPDATE] Implemented a new method getValueAtKeyPath to retrieve values from nested structures in translation data. This method allows direct access to nested data using a key path string, enhancing data retrieval efficiency. For example, getValueAtKeyPath("menu.items.item1") will access the value of item1 within items in the menu map.

[BUG] Addressed an issue in the translation retrieval logic where the previous method did not adequately handle nested structures. The updated approach ensures accurate retrieval of nested translations, improving the robustness of translation handling.

[COMPLEX MERGE] Refined the algorithm for extracting plural forms from translation data, allowing for more efficient access to pluralized translation entries. This enhancement is particularly useful for languages with complex pluralization rules, ensuring accurate and efficient retrieval of the correct plural form. For instance, accessing different plural forms of a word is now more streamlined, like getValueAtKeyPath("messages.count", localizations) to fetch the appropriate plural form based on the count.

Judimax commented 5 months ago

To illustrate the use of the translate function with a JSON structure that includes nested objects and lists, we'll expand our example to include a more complex structure. In this scenario, our localization files will have keys that point to nested objects and arrays, providing a more intricate example of how you might structure your localized content.

Here's an extended example of the JSON structure for English (en.json) and Spanish (es.json), including lists within the nested objects:

en.json:

{
  "dashboard": {
    "title": "Dashboard",
    "notifications": [
      "You have {count} new messages",
      "Your subscription ends in {days} days",
      "New features available!"
    ],
    "menu": {
      "home": "Home",
      "profile": "Profile",
      "settings": "Settings"
    }
  }
}

es.json:

{
  "dashboard": {
    "title": "Tablero",
    "notifications": [
      "Tienes {count} mensajes nuevos",
      "Tu suscripción termina en {days} días",
      "¡Nuevas funciones disponibles!"
    ],
    "menu": {
      "home": "Inicio",
      "profile": "Perfil",
      "settings": "Configuración"
    }
  }
}

Now, let's see how we can use the translate function to access these nested and list-based translations:

Accessing Nested Objects

To access translations within nested objects, you concatenate the keys using a dot (.) notation.

String title = translate("dashboard.title");
print(title); // Outputs "Dashboard" in English or "Tablero" in Spanish.

Accessing List Items

To access a specific item in a list, you would also use the dot notation followed by the index of the item in the list.

String messageNotification = translate("dashboard.notifications.0", args: {"count": 5});
print(messageNotification); // Outputs "You have 5 new messages" in English or "Tienes 5 mensajes nuevos" in Spanish.

String subscriptionNotification = translate("dashboard.notifications.1", args: {"days": 3});
print(subscriptionNotification); // Outputs "Your subscription ends in 3 days" in English or "Tu suscripción termina en 3 días" in Spanish.

Accessing Nested Object within an Array

If the JSON structure had nested objects within an array, you would access them similarly, using the index and then the key.

Assuming a structure (not shown in the above JSON) like "dashboard": { "alerts": [{ "type": "System", "message": "An update is available." }] }, you would access the message like so:

String alertMessage = translate("dashboard.alerts.0.message");
print(alertMessage); // Assuming it's properly localized, it would output the message based on the current locale.

These examples demonstrate how you can retrieve localized strings from a complex JSON structure using the translate function, accommodating dynamic values and accessing nested and array-based structures.

Judimax commented 5 months ago

To illustrate how to use the translatePlural function for handling pluralization based on the current locale, we'll need to expand our JSON structure to include plural forms. The Intl package, which is often used in conjunction with localization in Flutter, supports different plural categories like zero, one, two, few, many, and other, depending on the language's rules.

Let's modify our en.json and es.json files to include a pluralized key for messages. Note that different languages have different pluralization rules, and the Intl package expects you to provide translations for all these categories, even if they're not all used in every language.

en.json:

{
  "dashboard": {
    "title": "Dashboard",
    "messages": {
      "zero": "You have no new messages",
      "one": "You have one new message",
      "other": "You have {count} new messages"
    },
    "menu": {
      "home": "Home",
      "profile": "Profile",
      "settings": "Settings"
    }
  }
}

es.json:

{
  "dashboard": {
    "title": "Tablero",
    "messages": {
      "zero": "No tienes mensajes nuevos",
      "one": "Tienes un mensaje nuevo",
      "other": "Tienes {count} mensajes nuevos"
    },
    "menu": {
      "home": "Inicio",
      "profile": "Perfil",
      "settings": "Configuración"
    }
  }
}

Now, here's how you can use the translatePlural function to select the appropriate plural form based on the number of messages:

String noMessages = translatePlural("dashboard.messages", 0);
print(noMessages); // Outputs "You have no new messages" in English or "No tienes mensajes nuevos" in Spanish.

String oneMessage = translatePlural("dashboard.messages", 1);
print(oneMessage); // Outputs "You have one new message" in English or "Tienes un mensaje nuevo" in Spanish.

String multipleMessages = translatePlural("dashboard.messages", 5, args: {"count": 5});
print(multipleMessages); // Outputs "You have 5 new messages" in English or "Tienes 5 mensajes nuevos" in Spanish.

In this code, translatePlural is used to get the correct translation based on the value provided. The value determines which plural form to use, and the optional args map allows for dynamic values to be inserted into the translation string, similar to how you use translate for non-pluralized strings.

Judimax commented 5 months ago

1. Using Lists: Displaying Localized Options in a ListView

Given a list in your localization files like this:

{
  "options": ["Option 1", "Option 2", "Option 3"]
}

You can create a ListView that dynamically translates each item:

Widget buildOptionList(BuildContext context) {
  // Assuming the number of options is known
  int optionCount = 3;

  return ListView.builder(
    itemCount: optionCount,
    itemBuilder: (context, index) {
      return ListTile(
        title: Text(translate('options.$index')),
      );
    },
  );
}

2. Using Maps: Dynamic Key-Value Localization

Consider a map in your localization files:

{
  "settings": {
    "volume": "Volume",
    "brightness": "Brightness"
  }
}

To display this in a UI component like ListView, you can:

Widget buildSettingsList(BuildContext context) {
  Map<String, String> settings = getValueAtKeyPath('settings');

  return ListView.builder(
    itemCount: settings.length,
    itemBuilder: (context, index) {
      String key = settings.keys.elementAt(index);
      return ListTile(
        title: Text(translate(key)),
        subtitle: Text(translate("settings.$key")),
      );
    },
  );
}

3. Combining Lists and Maps: Nested Localization

Assuming a combination of lists and maps:

{
  "categories": ["books", "movies"],
  "books": ["Fiction", "Non-fiction"],
  "movies": ["Action", "Comedy"]
}

Displaying nested structures in a UI, such as ExpansionTile:

Widget buildCategoryList(BuildContext context) {
  List<String> categories = getValueAtKeyPath('categories');

  return ListView.builder(
    itemCount: categories.length,
    itemBuilder: (context, index) {
      String category = categories[index];
      List<String> subcategories = getValueAtKeyPath(category);

      return ExpansionTile(
        title: Text(translate(category)),
        children: subcategories.map((subcategory) {
          return ListTile(
            title: Text(translate('$category.$subcategory')),
          );
        }).toList(),
      );
    },
  );
}

In these examples, the translate function is used in a way that respects the dynamic nature of the list and map structures, ensuring that the localized strings are fetched correctly and dynamically based on the index or keys within the data structures.

Judimax commented 5 months ago

To create examples that use getValueAtKeyPath with translatePlural, we'll leverage the dynamic nature of Dart's access to nested JSON structures. We'll assume the JSON localization files contain pluralized strings and demonstrate how to construct key paths dynamically to use with translatePlural. Let's work through examples using Lists, Maps, and a combination of both.

1. Using Lists: Displaying Pluralized Localized Options

Given a list with pluralized keys in your localization files like this:

{
  "messages": [
    {"key": "inbox_message", "zero": "No messages", "one": "One message", "other": "{} messages"},
    {"key": "sent_message", "zero": "No messages sent", "one": "One message sent", "other": "{} messages sent"}
  ]
}

You can create a method to display the pluralized messages based on the count:

Widget buildMessageList(BuildContext context, List<int> messageCounts) {
  return ListView.builder(
    itemCount: messageCounts.length,
    itemBuilder: (context, index) {
      String keyPath = 'messages.$index.key';
      int count = messageCounts[index];
      return ListTile(
        title: Text(translatePlural(keyPath, count)),
      );
    },
  );
}

2. Using Maps: Pluralized Settings Localization

Consider a map in your localization files with pluralized values:

{
  "notifications": {
    "email": {"zero": "No emails", "one": "One email", "other": "{} emails"},
    "alert": {"zero": "No alerts", "one": "One alert", "other": "{} alerts"}
  }
}

Display these settings with pluralization:

Widget buildNotificationSettings(BuildContext context, Map<String, int> notificationCounts) {
  return ListView.builder(
    itemCount: notificationCounts.length,
    itemBuilder: (context, index) {
      String key = notificationCounts.keys.elementAt(index);
      int count = notificationCounts[key];
      return ListTile(
        title: Text(translatePlural('notifications.$key', count)),
      );
    },
  );
}

3. Combining Lists and Maps: Nested Pluralized Localization

Assume a combination of lists and maps for categories and their items, with pluralization:

{
  "categories": ["emails", "alerts"],
  "emails": {"zero": "No emails", "one": "One email", "other": "{} emails"},
  "alerts": {"zero": "No alerts", "one": "One alert", "other": "{} alerts"}
}

Displaying these in a UI, such as an ExpansionTile, with counts:

Widget buildCategoryList(BuildContext context, Map<String, int> itemCounts) {
  List<String> categories = getValueAtKeyPath('categories');

  return ListView.builder(
    itemCount: categories.length,
    itemBuilder: (context, index) {
      String category = categories[index];
      int count = itemCounts[category] ?? 0;

      return ExpansionTile(
        title: Text(translatePlural(category, count)),
      );
    },
  );
}

In these examples, translatePlural is dynamically used with key paths constructed on the fly, similar to how translate was used in your provided examples, enabling the handling of pluralized translations in a dynamic and nested data structure.

bratan commented 3 months ago

I`m going to close this since nobody is going to review 45+ ChatGPT-generated files and commits with a "CHECKPOINT" message.