• Stars
    star
    124
  • Rank 279,206 (Top 6 %)
  • Language
    Java
  • License
    Apache License 2.0
  • Created almost 5 years ago
  • Updated 8 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Extracts localized strings from an Android app and stores it in a much more efficient format.

StringPacks

StringPacks is a library to store translation strings in a more efficient binary format for Android applications, so that it reduces the Android APK size.

Check out our tech talk on StringPacks from DroidCon SF 2019 to know more about the motivation, architecture and prospect of the StringPacks project.

Requirements

  • Python 3 - The StringPacks python scripts are written in Python 3.
  • minSdkVersion 15 - The library default min sdk version is 15, but it should work for lower SDK versions.
  • Git - The script uses git ls-files to look up files.
  • Android development environment
  • Gradle Build System

Setup in Android Project

  1. Copy the scripts/ and pack.gradle from library/ to the root directory of your Android project.
  2. Move either Java or Kotlin version of StringPackIds file from templates/ directory to your project source code directory.
    • Edit package information of the file.
  3. Move template config.json to your Android application project directory.
    • Replace {app} to be your application project directory name.
    • Choose one of the two (mutually exclusive):
      • Point pack_ids_class_file_path to the path where you put the StringPackIds file.
      • Configure resource_config_setting to generate the necessary aapt config file.
        • config_file_path file path for aapt2 config to set stable id
        • source_file_path for file path of generated Java source
        • string_offset a hex string for string id offset (usually "0x7f120000")
        • plurals_offset a hex string for plural id offset (usually "0x7f100000")
        • package_name for package name.
  4. Make following changes to your Android project's build.gradle.
    allprojects {
    
      repositories {
        ...
        mavenCentral()
     }
     ...
    }
    
    // Replace `{path_to_config.json}` with the path to your `config.json` file
    ext {
      stringPacksConfigFile = "$rootDir/{path_to_config.json}"
    }
    
    • Replace {path_to_config.json} with the path to your config.json file
  5. Make following changes to your Android application's build.gradle
    apply from: "$rootDir/pack.gradle"
    
    dependencies {
      ...
      ...
      implementation 'com.whatsapp.stringpacks:stringpacks:0.3.1'
    }
    
  6. To remove old .pack files from the device's internal storage, on every app upgrade, add MyPackageReplacedReceiver.java and PackFileDeletionService.java to your AndroidManifest.xml
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <application ...>
      ...
      <receiver android:name="com.whatsapp.stringpacks.receiver.MyPackageReplacedReceiver">
        <intent-filter>
            <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
        </intent-filter>
      </receiver>
      <service android:name="com.whatsapp.stringpacks.service.PackFileDeletionService" android:permission="android.permission.BIND_JOB_SERVICE" />
    </application>
    

Note: If you want to delete old .pack files, from internal storage, at some other time instead of app upgrade, call StringPacks.cleanupOldPackFiles(getApplicationContext()) whenever you want. You don't have to include MyPackageReplacedReceiver.java or PackFileDeletionService.java in your AndroidManifest.xml

You now have StringPacks available in your Android project.

Getting Started

There are a few steps to walk through before you can really use packed strings in your application. But don't worry, most of them only need to be done once.

Runtime

Since the translated strings are moved to our special binary format (.pack files), your application needs a way to read those strings during runtime. The library provides a wrapper class for Context and Resources to help with that.

You need to add the following code to all subclasses of your Context class (like Activity and Service) to ensure the strings are read from .pack files instead of Android system resources.

// Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(StringPackContext.wrap(base));
}
// Kotlin

override fun attachBaseContext(base: Context?) {
    super.attachBaseContext(StringPackContext.wrap(base))
}

If all of the following conditions meet, you need to override getResources() function also in your Activity

  1. App's minSdkVersion is < 17
  2. You have a dependency on androidx.appcompat:appcompat:1.2.0
  3. Your Activity extends from AppCompatActivity
// Java

private @Nullable StringPackResources stringPackResources;
@Override
public Resources getResources() {
  if (stringPackResources == null) {
    stringPackResources = StringPackResources.wrap(super.getResources());
  }
  return stringPackResources;
}
// Kotlin

private @Nullable var stringPackResources:Resources? = null
override fun getResources(): Resources? {
  if (stringPackResources == null) {
    stringPackResources = StringPackResources.wrap(super.getResources())
  }
  return stringPackResources
}

Your Android application also needs to use a custom Application, which needs to include the following code to ensure the strings are read from .pack files.

// Java

@Override
protected void attachBaseContext(Context base) {
  StringPackIds.registerStringPackIds();
  StringPacks.getInstance().setUp(base);

  super.attachBaseContext(base);
}

private @Nullable StringPackResources stringPackResources;
@Override
public Resources getResources() {
  if (stringPackResources == null) {
    stringPackResources = StringPackResources.wrap(super.getResources());
  }
  return stringPackResources;
}
// Kotlin

override fun attachBaseContext(base: Context?) {
    registerStringPackIds()
    StringPacks.getInstance().setUp(base)

    super.attachBaseContext(base)
}

private @Nullable var stringPackResources:Resources? = null
override fun getResources(): Resources? {
  if (stringPackResources == null) {
    stringPackResources = StringPackResources.wrap(super.getResources())
  }
  return stringPackResources
}

You only need to do this each time you add a new context component. You don't need to do this for each component if you add them to a base class.

Region specific locales & Fallback

You can map multiple regions into a single .pack file using pack_id_mapping in config.json. For example

pack_id_mapping = {
  "es-rMX": "es",
  "es-rES": "es"
}

Here, translations in "es", "es-MX" and "es-ES" locales would be packed into strings_es.pack file.

If you are supporting any of the following features, you need to implement StringPacksLocaleMetaDataProvider.java and register the provider in your custom Application class

  1. Packing translations for multiple locales (for example, es-MX, es) in to one .pack file, or
  2. Fallback feature, or
  3. Supporting region specific locales
// Java

@Nullable private final StringPacksLocaleMetaDataProvider metaData = new LocaleMetaDataProviderImpl();
@Override
protected void attachBaseContext(Context base) {
  StringPackIds.registerStringPackIds();
  StringPacks.registerStringPackLocaleMetaData(metaData);
  StringPacks.getInstance().setUp(base);

  super.attachBaseContext(base);
}
// Kotlin

private @Nullable var metaData:StringPacksLocaleMetaDataProvider? = LocaleMetaDataProviderImpl()
override fun attachBaseContext(base: Context?) {
  registerStringPackIds();
  StringPacks.registerStringPackLocaleMetaData(metaData);
  StringPacks.getInstance().setUp(base);

  super.attachBaseContext(base);
}

Take a look at LocaleMetaDataProviderImpl.java in the sample app for reference.

Generate .pack files

You have added the StringPackIds file to your project, but it has nothing in it yet. It is supposed to hold the mapping from android resource IDs (R.string) to string pack IDs. The content would be automatically filled in when you run the script that provided by this library. The mapping information would also be used for generating the .pack files, so they are correctly loaded at runtime.

Execute the python script from your project root directory to assemble the string packs:

python3 ./scripts/assemble_string_packs.py --config ./{path_to}/config.json

You will see:

  • The StringPackIds file has been updated with the pack ID mapping information;
  • The translation strings, which are packable, have been moved to different directory, so that they won't be compiled into the APK;
  • The .pack file for different language have been generated under the project assets/ directory.

When you update translations, or change a string in the project, you may run the script again to generate .pack files with latest content.

Those string resource IDs that are not listed in the StringPackIds file, will continue to be kept in the Android system resources, and the StringPacks runtime would automatically fall back to read from there.

 

Now, you can use gradle to build your application as usual. The application should correctly retrieve the strings from StringPacks.

License

Copyright (c) Facebook, Inc. and its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

More Repositories

1

stickers

This repository contains the iOS and Android sample apps and API for creating third party sticker packs for WhatsApp.
Java
2,651
star
2

proxy

This repository contains the WhatsApp proxy implementation for users to host their own proxy infrastructure to connect to WhatsApp for chat (VoIP is not currently supported)
Shell
1,163
star
3

waraft

An Erlang implementation of RAFT from WhatsApp
Erlang
508
star
4

eqwalizer

A type-checker for Erlang
Erlang
477
star
5

erlfmt

An automated code formatter for Erlang
Erlang
391
star
6

WhatsApp-Business-API-Setup-Scripts

The scripts related to setting up WhatsApp business API
373
star
7

erlt

Early prototype of ErlT, an experimental Erlang dialect with first-class support for static typing.
Erlang
127
star
8

erlfuzz

erlfuzz is a fuzzer for the Erlang ecosystem
Rust
112
star
9

WhatsApp-Nodejs-SDK

The official Meta Business Messaging WhatsApp Cloud API Node.js SDK.
TypeScript
102
star
10

erlang-language-platform

Erlang Language Platform. LSP server and CLI.
Rust
77
star
11

power_shell

Erlang shell with advanced features: evaluating non-exported functions and shortcuts for frequently used functions.
Erlang
74
star
12

tree-sitter-erlang

Tree-sitter Grammar for Erlang
C
65
star
13

WADebug

WhatsApp Debug - A command-line tool that will be used to troubleshoot installation of WhatsApp Business API.
Python
30
star
14

whatsapp-business-api-deployment-templates

This repo hosts the cloud templates which enable one-click deployment of their WhatsApp Business Platform On-Premise API on different cloud platforms with stable high messaging throughput.
HCL
24
star
15

erldist_filter

erldist_filter NIF for filtering and logging Erlang Dist Protocol messages
C
13
star
16

WhatsApp-OTP-Sample-App

Sample app that integrates with WhatsApp OTP (One-Time Password) copy code and "one-tap" autofill features. This project shows how to send and receive OTP code from WhatsApp and best practices around integration.
Swift
10
star
17

WhatsApp-Android-OTP-SDK

WhatsApp Android OTP SDK helps you integrate with one-time password solution provided by WhatsApp.It provides handy functions that simplifies the integration work.
Java
4
star
18

WhatsApp-Flows-Tools

Tools and examples to help you create WhatsApp Flows https://developers.facebook.com/docs/whatsapp/flows
1
star