Lorsqu’une application mobile est déployée sur plusieurs marchés, la gestion des métadonnées devient un point critique du processus de publication. Chaque store : Google Play, App Store iOS ou Mac App Store, impose ses propres contraintes de structure, de taille, de format et de nommage. Maintenir ces fichiers à jour manuellement pour chaque langue est source d’erreurs. D’où l’intérêt d’une génération automatisée des métadonnées multilingues, adossée à une arborescence standardisée et à une logique unique de mappage des locales.
Pourquoi structurer et automatiser ?
- Respect automatique des contraintes de chaque store.
- Cohérence entre toutes les langues et tous les marchés.
- Processus de mise à jour simplifié lors des releases.
- Moins de rejets de publication grâce à une validation en amont.
Arborescence de référence (Fastlane)
Référence basée sur la structure réellement utilisée sous build/fastlane/metadata.
Android (Google Play Store)
build/
└── fastlane/
└── metadata/
└── android/
├── af/
│ ├── changelogs/
│ │ ├── 273.txt
│ │ ├── 274.txt
│ │ └── 275.txt
│ ├── images/
│ ├── full_description.txt
│ ├── keywords.txt
│ ├── review_information.txt
│ ├── short_description.txt
│ └── title.txt
├── am/
├── fr-FR/
└── ...
- Un dossier par locale (ex.
af,fr-FR,en-US). changelogs/: un fichier par versionCode (273.txt,274.txt, …).- Champs texte Google Play :
title.txt(≤ 30),short_description.txt(≤ 80),full_description.txt(≤ 4000). keywords.txt,review_information.txt: utiles pour ton pipeline interne (ignorés par Fastlane sans erreur).
iOS / macOS (App Store Connect)
build/
└── fastlane/
└── metadata/
└── ios/
├── ar-SA/
│ ├── description.txt
│ ├── keywords.txt
│ ├── marketing_url.txt
│ ├── name.txt
│ ├── privacy_url.txt
│ ├── promotional_text.txt
│ ├── release_notes.txt
│ ├── subtitle.txt
│ └── support_url.txt
├── ca/
├── de-DE/
└── ...
- Champs texte App Store :
name.txt(≤ 30),subtitle.txt(≤ 30),description.txt(≤ 4000),keywords.txt(≤ 100, séparés par virgules),promotional_text.txt(≤ 170, optionnel),release_notes.txt. - URLs :
support_url.txt,privacy_url.txt,marketing_url.txt(HTTPS valides).
Nommage des dossiers de langues (locales)
Les noms de dossiers doivent respecter les formats officiels des stores, qui diffèrent souvent de ceux utilisés dans le projet (fr, en, pt_BR, etc.). Exemples :
fr→fr-FRen→en-USpt→pt-BR(Android par défaut dans ton mapping),pt-PT(Apple si nécessaire)zh→zh-CN(Android),zh-Hans(Apple)
Règles :
- Utiliser des tirets (
-) et non des underscores (_). - Langue en minuscule, région en MAJUSCULE :
en-US. - Ne créer de dossiers que pour des locales supportées par le store.
Bibliothèque Dart — mappage des locales (source de vérité)
Ce bloc gère la conversion des locales du projet vers les formats attendus par App Store Connect et Google Play. Collez-le tel quel dans votre base de code.
/// Bibliothèque partagée pour mapper les locales du projet aux formats
/// attendus par le Google Play Store et l'App Store Connect.
/// C'est la source de vérité unique pour la logique de mappage.
// ===========================================================================
// LOGIQUE POUR APP STORE CONNECT (IOS)
// ===========================================================================
/// Mappage des locales du projet vers les locales spécifiques à Apple.
const Map<String, String> _appleLocaleMapping = {
'en': 'en-US', 'en-GB': 'en-GB', 'en-CA': 'en-CA', 'fr': 'fr-FR', 'fr-CA': 'fr-CA',
'es': 'es-ES', 'es-MX': 'es-MX', 'de': 'de-DE', 'it': 'it',
'pt': 'pt-PT', // Spécifique à Apple : Portugais (Portugal)
'pt-BR': 'pt-BR', 'zh': 'zh-Hans', 'zh-CN': 'zh-Hans', 'zh-HK': 'zh-Hant',
'zh-TW': 'zh-Hant', 'ja': 'ja', 'ko': 'ko', 'ru': 'ru', 'ar': 'ar-SA',
'nl': 'nl-NL', 'sv': 'sv', 'fi': 'fi', 'da': 'da', 'no': 'no', 'tr': 'tr',
'pl': 'pl', 'id': 'id', 'th': 'th', 'vi': 'vi', 'he': 'he', 'ms': 'ms',
'ro': 'ro', 'cs': 'cs', 'sk': 'sk', 'hr': 'hr', 'uk': 'uk', 'hi': 'hi',
'el': 'el', 'ca': 'ca', 'et-EE': 'et', 'uk-UA': 'uk',
};
/// Liste des locales officiellement supportées par deliver/App Store Connect.
const Set<String> _appStoreSupportedLocales = {
'ar-SA', 'ca', 'cs', 'da', 'de-DE', 'el', 'en-AU', 'en-CA', 'en-GB', 'en-US',
'es-ES', 'es-MX', 'fi', 'fr-CA', 'fr-FR', 'he', 'hi', 'hr', 'hu', 'id', 'it',
'ja', 'ko', 'ms', 'nl-NL', 'no', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk',
'sv', 'th', 'tr', 'uk', 'vi', 'zh-Hans', 'zh-Hant', 'et'
};
/// Convertit une locale du projet en sa version pour l'App Store.
String? toAppleLocale(String locale) {
if (_appleLocaleMapping.containsKey(locale)) {
final appleLocale = _appleLocaleMapping[locale]!;
// Si la locale mappée est supportée, on la retourne, sinon, échec.
return _appStoreSupportedLocales.contains(appleLocale) ? appleLocale : null;
}
if (_appStoreSupportedLocales.contains(locale)) return locale;
return null; // Fallback : la locale n'est pas supportée.
}
/// ===========================================================================
// LOGIQUE POUR GOOGLE PLAY (ANDROID) - CORRIGÉ
// ===========================================================================
/// Mappage des locales du projet vers les locales spécifiques à Google Play.
/// CORRIGÉ : Cette table est maintenant basée sur la liste officielle de votre
/// fiche Play Store. Elle résout les ambiguïtés (ex: 'fr' -> 'fr-FR') et
/// mappe les codes simples vers eux-mêmes si c'est le format attendu.
const Map<String, String> _playLocaleMapping = {
// Mappages directs (le code simple est le format attendu)
'af': 'af', 'am': 'am', 'bg': 'bg', 'ca': 'ca','lv': 'lv',
'hr': 'hr', 'et': 'et', 'el': 'el-GR',
'lt': 'lt', 'ms': 'ms', 'ro': 'ro',
'sr': 'sr', 'sk': 'sk', 'sl': 'sl', 'sw': 'sw',
'fil': 'fil', 'cs': 'cs-CZ', 'th': 'th', 'uk': 'uk', 'vi': 'vi',
'zu': 'zu','gu':'gu','kk':'kk','pa':'pa','sq':'sq','ur':'ur',
'ar':'ar',
// Gestion de l'Indonésien (id/in)
'id': 'id',
'in': 'id', // 'in' est un ancien code pour 'id'
// Mappages pour résoudre les ambiguïtés ou suivre des règles spécifiques
'fr': 'fr-FR', // Instruction explicite : fr -> fr-FR
'en': 'en-US', // Défaut pour 'en'
'es': 'es-ES', // Défaut pour 'es'
'pt': 'pt-BR', // Défaut pour 'pt'
'zh': 'zh-CN', // Défaut pour 'zh'
'sv': 'sv-SE',
'ta' : 'ta-IN',
'te': 'te-IN', // Défaut pour 'ta-IN"
'tr': 'tr-TR',
'de': 'de-DE',
'fi': 'fi-FI',
'hi': 'hi-IN',
'hu': 'hu-HU',
'is': 'is-IS',
'it': 'it-IT',
'ja': 'ja-JP',
'ko': 'ko-KR',
'nl': 'nl-NL',
'no' :'no-NO',
'pl': 'pl-PL',
'ru': 'ru-RU',
'da': 'da-DK',
'az':"az-AZ",
'be':'be',
'bn':'bn-BD',
'eu':'eu-ES',
'fa':'fa-IR',
'gl':'gl-ES',
'he':"iw-IL",
'hy':"hy-AM",
'ka':'ka-GE',
'km':'km-KH',
'kn':'kn-IN',
'ky':'ky-KG',
'lo':'lo-LA',
'mk':'mk-MK',
'ml':'ml-in',
'mn':'mn-MN',
'mr':'mr-in',
'my':'my-MM',
'ne':'ne-NP',
'si':'si-LK',
// Mappages régionaux déjà corrects et supportés
'en-US': 'en-US', 'en-GB': 'en-GB', 'zh-HK': 'zh-HK', 'zh-CN': 'zh-CN',
'zh-TW': 'zh-TW', 'es-419': 'es-419', 'es-ES': 'es-ES', 'fr-CA': 'fr-CA',
'fr-FR': 'fr-FR', 'pt-BR': 'pt-BR', 'pt-PT': 'pt-PT',
};
/// CORRIGÉ : Liste des locales officiellement supportées par VOTRE Google Play Store.
const Set<String> _playSupportedLocales = {
'be','ar','af','az', 'de', 'am', 'en-US', 'en-GB', 'bg', 'ca', 'zh-HK', 'zh-CN', 'zh-TW',
'ko', 'hr', 'da', 'es-419', 'es-ES', 'et', 'fi', 'fr-CA', 'fr-FR', 'el','si',
'hi', 'hu', 'id', 'in', 'is', 'it', 'ja', 'lv', 'lt', 'ms', 'nl','mk','my','ne',
'no', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sr', 'sk', 'sl', 'sv', 'sw','ka','km','kn','ky','lo','mn','mr',
'fil', 'cs', 'th', 'tr', 'uk', 'vi', 'zu','gu','kk','pa','sq','ta','te','ur','bn','eu','fa','gl','he','hy','ml'
};
/// Convertit une locale du projet en sa version pour le Google Play Store.
String? toPlayLocale(String locale) {
// Normalise la locale (ex: ja_JP -> ja-JP)
final normalizedLocale = locale.replaceAll('_', '-');
// 1. Mappage explicite (ex: 'ja' -> 'ja-JP')
if (_playLocaleMapping.containsKey(normalizedLocale)) {
return _playLocaleMapping[normalizedLocale];
}
// 2. Locale déjà supportée telle quelle (ex: 'fr-CA')
if (_playSupportedLocales.contains(normalizedLocale)) {
return normalizedLocale;
}
// 3. Non supportée
return null;
}
Exemple d’utilisation : création automatique des dossiers
Exemple minimal pour générer les dossiers de langues conformes sous build/fastlane/metadata :
import 'dart:io';
void main() {
final locales = ['af', 'fr', 'en', 'pt-BR', 'zh'];
for (final locale in locales) {
final ios = toAppleLocale(locale);
final play = toPlayLocale(locale);
if (ios != null) {
Directory('build/fastlane/metadata/ios/$ios').createSync(recursive: true);
print('✅ Dossier iOS créé : build/fastlane/metadata/ios/$ios');
}
if (play != null) {
Directory('build/fastlane/metadata/android/$play').createSync(recursive: true);
Directory('build/fastlane/metadata/android/$play/changelogs').createSync(recursive: true);
print('✅ Dossier Android créé : build/fastlane/metadata/android/$play');
}
}
}
Contraintes de contenu (rappel)
Google Play Store
| Fichier | Description | Limite / Format |
|---|---|---|
title.txt | Titre de l’application | ≤ 30 caractères |
short_description.txt | Description courte affichée dans le store | ≤ 80 caractères |
full_description.txt | Description complète de la fiche Google Play | ≤ 4000 caractères |
changelogs/<versionCode>.txt | Notes de version spécifiques à chaque build | Un fichier par versionCode (ex. 273.txt) |
images/ | Visuels : icône, feature graphic, captures d’écran | icon 512×512 px, featureGraphic 1024×500 px |
App Store (iOS / macOS)
| Fichier | Description | Limite / Format |
|---|---|---|
name.txt | Nom de l’application | ≤ 30 caractères |
subtitle.txt | Sous-titre affiché dans la fiche App Store | ≤ 30 caractères |
description.txt | Description complète de la fiche | ≤ 4000 caractères |
keywords.txt | Mots-clés pour le référencement interne | ≤ 100 caractères, séparés par virgules |
promotional_text.txt | Texte promotionnel temporaire | ≤ 170 caractères (optionnel) |
release_notes.txt | Notes de version associées à la build | Texte libre |
support_url.txt | Lien vers le support utilisateur | URL HTTPS valide |
privacy_url.txt | Politique de confidentialité | URL HTTPS valide |
marketing_url.txt | Page de marketing externe | URL HTTPS valide (optionnel) |
Bonnes pratiques
- Utiliser des tirets (
-) pour les locales (ex.fr-FR), jamais d’underscores. - Langue en minuscule, région en MAJUSCULE (ex.
en-US). - Valider la taille des champs avant publication.
- Centraliser les textes (JSON, CSV,
.arb) et générer automatiquement. - Ne créer des dossiers que pour des locales supportées par le store.
Avec cette structure et ce mappage, la génération multilingue des métadonnées devient fiable, cohérente et parfaitement compatible avec Fastlane pour Google Play et App Store.



