Overview of the Stores application

This web application is used to maintain localized content for mobile stores (Google Play, App Store):

  • Main listing (description).
  • What’s new content for new releases.
  • Keywords.
  • Other localizable content, like screenshots.

Localization files are stored in the appstores l10n repository (.lang format).

This app allows:

  • Localizers to see the various strings assembled in one page/template, and warnings for strings exceeding length limits.
  • L10n drivers to check the overall status of store localization for each product and channel. In the main view, completion cell turns green when localization is complete.
  • Release Engineering to get translations through API. This is currently happening only for Android, via mozapkpublisher, but there are plans to extend it to App Store content.
  • Other users, for example designers working on screenshots, to copy and paste translation without accessing directly repositories.

Refer to the README in the code repository for instructions on how to install this app.

Projects configuration

Configuration is split between app/config/product_sources.json and the Project class (app/classes/Stores/Project.php).

app/config/product_sources.json contains a list of supported products and channels, together with a URL that can be used to retrieve the list of shipping locales. JSON has the following structure:

  "product code name": [
      "channel": "name of the channel",
      "format": "format of the remote list",
      "source": "URL of the remote list"

app/scripts/update_shipping_locales.py can be used to generate, or update, the list of shipping locales which is stored in app/config/shipping_locales.json. It’s important to note that these lists are used in Langchecker, to determine which locales should see specific files (product pages on mozilla.org, store content).

$products_data stores all supported products, with the following structure for each of them:

'fx_android' =>
        'channels' => ['beta', 'release'],
        'name'     => 'Firefox for Android',
        'store'    => 'google',

Relevant information in this array:

  • The ID of the project, used also for API calls, it’s the item’s key.
  • Supported channels.
  • Full name of the product, used in views and navigation.
  • In which store the project lives. Currently it can be apple or google.

$locales_mapping includes information on mappings between Mozilla’s locale codes, and each store’s internal codes.

$templates determines, for each product and channel, which template is used, and which .lang files are associated to a specific part of the template (What’s new, main listing, screenshots). This array is updated every time there is a new release.

In order to add a brand new project, you would need to:

  • Define the new project and supported channels in $products_data.
  • Define the list of supported locales for each channel in $supported_locales.
  • Define an entry in $templates.
  • Create files in /templates and /view for each supported channel.
  • Track the new .lang files in Langchecker for the appstores repository.

Template and View Structure

This is a portion of the template file used for Focus for iOS.

$screenshots = function ($translations) use ($_) {
    return <<<OUT
{$_('Automatically block ads<br>& other Web trackers')}

{$_('Browse Faster<br>Web pages may load faster<br>by removing trackers')}




{$_('From Mozilla<br>A brand you trust')}

Similarly to a Django template, each string is wrapped in a translation function $_('') (defined in app/inc/utilities.php). Strings are grouped into a variable, like $screenshots in this case: this allows, for example, to determine the overall length of all strings included in a section, to see if it exceeds the limits imposed by stores. It’s important to note that, since these limits are per-section and not per-string, the standard webdashboard won’t display any warnings. Also, if a string exceeds the maximum length, it won’t be exposed via API and the locale will be marked as incomplete.

These variables are then used in views, for example the variable/function $description:

<pre <?= $direction ?>><?= $description($translations) ?></pre>

It’s also possible to embed more logic into these view:

        Check if the file used for screenshots exists, display this section
        only in that case.
    $screenshot_lang = $project->getLangFiles($request['locale'], $request['product'], $request['channel'], 'screenshots');
    if ($screenshot_lang) {
        $locale_file = LOCALES_PATH . $request['locale'] . '/' . array_shift($screenshot_lang);
        if (file_exists($locale_file)) {
            <pre <?= $direction ?> class="text-center"><?= $screenshots($translations) ?></pre>


In this specific case the Screenshots section is displayed only if locale has the associated .lang file. This allows to use only one view for all languages, even if only some locales will have the screenshots .lang file.