Index: build.py
===================================================================
--- a/build.py
+++ b/build.py
@@ -252,7 +252,6 @@
         localeConfig = {
             'name_format': 'BCP-47',
             'file_format': 'gecko-dtd',
-            'target_platforms': {'gecko'},
             'default_locale': packager.defaultLocale
         }
     elif type in {'chrome', 'gecko-webext'}:
@@ -261,7 +260,6 @@
         localeConfig = {
             'name_format': 'ISO-15897',
             'file_format': 'chrome-json',
-            'target_platforms': {'chrome'},
             'default_locale': packager.defaultLocale,
         }
     else:
@@ -271,8 +269,6 @@
         localeConfig = {
             'name_format': metadata.get('locales', 'name_format'),
             'file_format': metadata.get('locales', 'file_format'),
-            'target_platforms': set(metadata.get('locales',
-                                                 'target_platforms').split()),
             'default_locale': metadata.get('locales', 'default_locale')
         }
 
@@ -344,7 +340,7 @@
 
     import buildtools.localeTools as localeTools
     for locale, localeDir in localeConfig['locales'].iteritems():
-        if locale != localeConfig['default_locale']:
+        if locale != localeConfig['default_locale'].replace('_', '-'):
             localeTools.uploadTranslations(localeConfig, metadata, localeDir, locale,
                                            basename, key)
 
Index: localeTools.py
===================================================================
--- a/localeTools.py
+++ b/localeTools.py
@@ -16,85 +16,29 @@
 from zipfile import ZipFile
 from xml.parsers.expat import ParserCreate, XML_PARAM_ENTITY_PARSING_ALWAYS
 
-langMappingGecko = {
-    'bn-BD': 'bn',
+CROWDIN_LANG_MAPPING = {
     'br': 'br-FR',
     'dsb': 'dsb-DE',
-    'fj-FJ': 'fj',
+    'es': 'es-ES',
+    'fur': 'fur-IT',
+    'fy': 'fy-NL',
+    'ga': 'ga-IE',
+    'gu': 'gu-IN',
     'hsb': 'hsb-DE',
-    'hi-IN': 'hi',
+    'hy': 'hy-AM',
     'ml': 'ml-IN',
-    'nb-NO': 'nb',
+    'nn': 'nn-NO',
+    'pa': 'pa-IN',
     'rm': 'rm-CH',
-    'ta-LK': 'ta',
-    'wo-SN': 'wo',
-}
-
-langMappingChrome = {
-    'es-419': 'es-MX',
-    'es': 'es-ES',
+    'si': 'si-LK',
     'sv': 'sv-SE',
-    'ml': 'ml-IN',
-    'gu': 'gu-IN',
+    'ur': 'ur-PK',
 }
 
-chromeLocales = [
-    'am',
-    'ar',
-    'bg',
-    'bn',
-    'ca',
-    'cs',
-    'da',
-    'de',
-    'el',
-    'en-GB',
-    'en-US',
-    'es-419',
-    'es',
-    'et',
-    'fa',
-    'fi',
-    'fil',
-    'fr',
-    'gu',
-    'he',
-    'hi',
-    'hr',
-    'hu',
-    'id',
-    'it',
-    'ja',
-    'kn',
-    'ko',
-    'lt',
-    'lv',
-    'ml',
-    'mr',
-    'ms',
-    'nb',
-    'nl',
-    'pl',
-    'pt-BR',
-    'pt-PT',
-    'ro',
-    'ru',
-    'sk',
-    'sl',
-    'sr',
-    'sv',
-    'sw',
-    'ta',
-    'te',
-    'th',
-    'tr',
-    'uk',
-    'vi',
-    'zh-CN',
-    'zh-TW',
-]
-
 CROWDIN_AP_URL = 'https://api.crowdin.com/api/project'
+FIREFOX_RELEASES_URL = 'http://www.mozilla.org/en-US/firefox/all.html'
+FIREFOX_LP_URL = 'https://addons.mozilla.org/en-US/firefox/language-tools/'
+CHROMIUM_DEB_URL = 'https://packages.debian.org/sid/all/chromium-l10n/filelist'
 
 
 def crowdin_request(project_name, action, key, get={}, post_data=None,
@@ -145,11 +89,6 @@
     return value.replace('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>').replace('&quot;', '"')
 
 
-def mapLocale(type, locale):
-    mapping = langMappingChrome if type == 'ISO-15897' else langMappingGecko
-    return mapping.get(locale, locale)
-
-
 def parseDTDString(data, path):
     result = []
     currentComment = [None]
@@ -304,38 +243,49 @@
 
 
 def setupTranslations(localeConfig, projectName, key):
-    # Make a new set from the locales list, mapping to Crowdin friendly format
-    locales = {mapLocale(localeConfig['name_format'], locale)
-               for locale in localeConfig['locales']}
-
-    # Fill up with locales that we don't have but the browser supports
-    if 'chrome' in localeConfig['target_platforms']:
-        for locale in chromeLocales:
-            locales.add(mapLocale('ISO-15897', locale))
+    locales = set()
 
-    if 'gecko' in localeConfig['target_platforms']:
-        firefoxLocales = urllib2.urlopen('http://www.mozilla.org/en-US/firefox/all.html').read()
-        for match in re.finditer(r'&amp;lang=([\w\-]+)"', firefoxLocales):
-            locales.add(mapLocale('BCP-47', match.group(1)))
-        langPacks = urllib2.urlopen('https://addons.mozilla.org/en-US/firefox/language-tools/').read()
-        for match in re.finditer(r'<tr>.*?</tr>', langPacks, re.S):
-            if match.group(0).find('Install Language Pack') >= 0:
-                match2 = re.search(r'lang="([\w\-]+)"', match.group(0))
-                if match2:
-                    locales.add(mapLocale('BCP-47', match2.group(1)))
+    # Languages supported by Firefox
+    data = urllib2.urlopen(FIREFOX_RELEASES_URL).read()
+    for match in re.finditer(r'&amp;lang=([\w\-]+)"', data):
+        locales.add(match.group(1))
 
-    allowed = set()
-    allowedLocales = crowdin_request(projectName, 'supported-languages', key)
+    # Languages supported by Firefox Language Packs
+    data = urllib2.urlopen(FIREFOX_LP_URL).read()
+    for match in re.finditer(r'<tr>.*?</tr>', data, re.S):
+        if match.group(0).find('Install Language Pack') >= 0:
+            match2 = re.search(r'lang="([\w\-]+)"', match.group(0))
+            if match2:
+                locales.add(match2.group(1))
 
-    for locale in allowedLocales:
-        allowed.add(locale['crowdin_code'])
+    # Languages supported by Chrome (excluding es-419)
+    data = urllib2.urlopen(CHROMIUM_DEB_URL).read()
+    for match in re.finditer(r'locales/(?!es-419)([\w\-]+)\.pak', data):
+        locales.add(match.group(1))
+
+    # We don't translate indvidual dialects of languages
+    # other than English, Spanish, Portuguese and Chinese.
+    for locale in list(locales):
+        prefix = locale.split('-')[0]
+        if prefix not in {'en', 'es', 'pt', 'zh'}:
+            locales.remove(locale)
+            locales.add(prefix)
+
+    # Add languages with existing translations.
+    locales.update(localeConfig['locales'])
+
+    # Don't add the language we translate from as target translation.
+    locales.remove(localeConfig['default_locale'].replace('_', '-'))
+
+    # Convert to locales understood by Crowdin.
+    locales = {CROWDIN_LANG_MAPPING.get(locale, locale) for locale in locales}
+    allowed = {locale['crowdin_code'] for locale in
+               crowdin_request(projectName, 'supported-languages', key)}
     if not allowed.issuperset(locales):
         print "Warning, following locales aren't allowed by server: " + ', '.join(locales - allowed)
 
-    locales = list(locales & allowed)
-    locales.sort()
+    locales = sorted(locales & allowed)
     params = urllib.urlencode([('languages[]', locale) for locale in locales])
-
     crowdin_request(projectName, 'edit-project', key, post_data=params)
 
 
@@ -429,7 +379,7 @@
             if data:
                 files.append((newName, data))
     if len(files):
-        language = mapLocale(localeConfig['name_format'], locale)
+        language = CROWDIN_LANG_MAPPING.get(locale, locale)
         data, headers = crowdin_prepare_upload(files)
         crowdin_request(projectName, 'upload-translation', key,
                         {'language': language}, post_data=data,
@@ -451,8 +401,8 @@
     normalizedDefaultLocale = localeConfig['default_locale']
     if localeConfig['name_format'] == 'ISO-15897':
         normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-')
-    normalizedDefaultLocale = mapLocale(localeConfig['name_format'],
-                                        normalizedDefaultLocale)
+    normalizedDefaultLocale = CROWDIN_LANG_MAPPING.get(normalizedDefaultLocale,
+                                                       normalizedDefaultLocale)
 
     for info in zip.infolist():
         if not info.filename.endswith('.json'):
@@ -470,12 +420,7 @@
             not origFile.endswith('.properties')):
             continue
 
-        if localeConfig['name_format'] == 'ISO-15897':
-            mapping = langMappingChrome
-        else:
-            mapping = langMappingGecko
-
-        for key, value in mapping.iteritems():
+        for key, value in CROWDIN_LANG_MAPPING.iteritems():
             if value == dir:
                 dir = key
         if localeConfig['name_format'] == 'ISO-15897':
Index: packagerChrome.py
===================================================================
--- a/packagerChrome.py
+++ b/packagerChrome.py
@@ -11,6 +11,7 @@
 import struct
 import sys
 import collections
+import glob
 
 from packager import (readMetadata, getDefaultFileName, getBuildVersion,
                       getTemplate, Files)
@@ -213,40 +214,14 @@
 
 
 def import_locales(params, files):
-    import localeTools
-
-    # FIXME: localeTools doesn't use real Chrome locales, it uses dash as
-    # separator instead.
-    convert_locale_code = lambda code: code.replace('-', '_')
-
-    # We need to map Chrome locales to Gecko locales. Start by mapping Chrome
-    # locales to themselves, merely with the dash as separator.
-    locale_mapping = {convert_locale_code(l): l for l in localeTools.chromeLocales}
-
-    # Convert values to Crowdin locales first (use Chrome => Crowdin mapping).
-    for chrome_locale, crowdin_locale in localeTools.langMappingChrome.iteritems():
-        locale_mapping[convert_locale_code(chrome_locale)] = crowdin_locale
-
-    # Now convert values to Gecko locales (use Gecko => Crowdin mapping).
-    reverse_mapping = {v: k for k, v in locale_mapping.iteritems()}
-    for gecko_locale, crowdin_locale in localeTools.langMappingGecko.iteritems():
-        if crowdin_locale in reverse_mapping:
-            locale_mapping[reverse_mapping[crowdin_locale]] = gecko_locale
-
-    for target, source in locale_mapping.iteritems():
-        targetFile = '_locales/%s/messages.json' % target
-        if not targetFile in files:
-            continue
-
-        for item in params['metadata'].items('import_locales'):
-            fileName, keys = item
-            parts = map(lambda n: source if n == '*' else n, fileName.split('/'))
-            sourceFile = os.path.join(os.path.dirname(item.source), *parts)
-            incompleteMarker = os.path.join(os.path.dirname(sourceFile), '.incomplete')
-            if not os.path.exists(sourceFile) or os.path.exists(incompleteMarker):
-                continue
-
-            data = json.loads(files[targetFile].decode('utf-8'))
+    for item in params['metadata'].items('import_locales'):
+        filename, keys = item
+        for sourceFile in glob.glob(os.path.join(os.path.dirname(item.source),
+                                                 *filename.split('/'))):
+            parts = sourceFile.split(os.path.sep)
+            locale = parts[-2].replace('-', '_')
+            targetFile = os.path.join('_locales', locale, 'messages.json')
+            data = json.loads(files.get(targetFile, '{}').decode('utf-8'))
 
             try:
                 # The WebExtensions (.json) and Gecko format provide
@@ -258,6 +233,7 @@
                         sourceData = json.load(handle)
                     import_string = import_string_webext
                 else:
+                    import localeTools
                     sourceData = localeTools.readFile(sourceFile)
                     import_string = import_string_gecko
 
@@ -296,10 +272,7 @@
     return text[:length_limit - 1].rstrip() + u'\u2026'
 
 
-def fixTranslationsForCWS(files):
-    # Chrome Web Store requires messages used in manifest.json to be present in
-    # all languages. It also enforces length limits for extension names and
-    # descriptions.
+def fix_translations_for_chrome(files):
     defaults = {}
     data = json.loads(files['_locales/%s/messages.json' % defaultLocale])
     for match in re.finditer(r'__MSG_(\S+)__', files['manifest.json']):
@@ -313,17 +286,30 @@
         if match:
             limits[match.group(1)] = limit
 
-    for filename in files:
-        if not filename.startswith('_locales/') or not filename.endswith('/messages.json'):
+    for path in list(files):
+        match = re.search(r'^_locales/(?:es_(AR|CL|(MX))|[^/]+)/(.*)', path)
+        if not match:
             continue
 
-        data = json.loads(files[filename])
-        for name, info in defaults.iteritems():
-            data.setdefault(name, info)
-        for name, limit in limits.iteritems():
-            if name in data:
-                data[name]['message'] = truncate(data[name]['message'], limit)
-        files[filename] = toJson(data)
+        # The Chrome Web Store requires messages used in manifest.json to
+        # be present in all languages, and enforces length limits on
+        # extension name and description.
+        is_latam, is_mexican, filename = match.groups()
+        if filename == 'messages.json':
+            data = json.loads(files[path])
+            for name, info in defaults.iteritems():
+                data.setdefault(name, info)
+            for name, limit in limits.iteritems():
+                info = data.get(name)
+                if info:
+                    info['message'] = truncate(info['message'], limit)
+            files[path] = toJson(data)
+
+        # Chrome combines Latin American dialects of Spanish into es-419.
+        if is_latam:
+            data = files.pop(path)
+            if is_mexican:
+                files['_locales/es_419/' + filename] = data
 
 
 def signBinary(zipdata, keyFile):
@@ -403,7 +389,7 @@
 
     files['manifest.json'] = createManifest(params, files)
     if type == 'chrome':
-        fixTranslationsForCWS(files)
+        fix_translations_for_chrome(files)
 
     if devenv:
         import buildtools
Index: tox.ini
===================================================================
--- a/tox.ini
+++ b/tox.ini
@@ -7,9 +7,9 @@
     build.py : A102,A104,A107,A201,A206,A302,E501,E711,F401,N802,N803,N806
     chainedconfigparser.py : A107,E501
     ensure_dependencies.py : A102,A107,A108,A302,E129,E501,E713,E721,F821
-    localeTools.py : A103,A104,A107,A108,A206,A301,A302,E129,E501,E711,E713,F401,F812,N802,N803,N806
+    localeTools.py : A103,A104,A107,A206,A301,A302,E129,E501,E711,E713,F401,N802,N803,N806
     packager.py : A102,A107,A206,A302,E501,E711,N802,N803,N806
-    packagerChrome.py : A101,A104,A105,A107,A108,A111,A112,A302,E129,E501,E711,E713,E731,F401,F841,N802,N803,N806
+    packagerChrome.py : A101,A103,A104,A107,A108,A111,A112,A302,E501,E711,F841,N802,N803,N806
     packagerGecko.py : A101,A104,A105,A107,A108,A201,A302,E501,E711,E713,F401,N802,N803,N806
     packagerSafari.py : A101,A107,A301,A302,E501,N802,N803,N806
     publicSuffixListUpdater.py : A108,E501,F821,N802,N803
