Publishing your dictionary

These guides assume you are comfortable with the Command Line, Git, Python and NPM. You must have all of these installed on your machine. You are also strongly encouraged to have a GitHub account. You are encouraged to fork or clone the Mother Tongues Dictionary Starter and follow along.

The simplest way to publish your app is on the web. Publishing to the Android and iOS app stores is an involved process. If you're just starting out, it's highly recommended that you start with the web or mobile (GitHub Pages) deployments first before publishing to Android or iOS.

To get started, you'll need to have prepared and built your dictionary.

note

This process for building apps, and the complexity that comes with it, is just a fact of app-building and would be the same for any other dictionary app you build. Sorry! Don't be mad at Mother Tongues! 😃

Mobile

Because the mobile MTD UI is built with Ionic, the code base stays the same for both Android and iOS builds. Therefore some of the changes only need to be done once.

File Structure

Note, the following is an incomplete look at the file structure of a typical mobile MTD UI. When you clone/fork it, there will be other files/folders. For the purpose of this guide, these are the ones you will need to know about.

📦mothertongues-UI
┣ 📂plugins
┣ 📂resources
┃ ┣ 📜icon.png
┃ ┗ 📜splash.png
┣ 📂src
┣ 📂www
┣ 📜config.xml
┣ 📜package.json

Cordova Configuration

Information about your app is kept in the config.xml file at the root of your project. The vast majority of this, you will never need to edit, although you can read more about Cordova configuration here if you're curious.

The parts you will need to edit are at the very beginning of the file:

<?xml version='1.0' encoding='utf-8'?>
<widget
android-packageName="com.YOUR_ORGANIZATION.YOUR_APP_SLUG"
android-versionCode="10000"
ios-CFBundleIdentifier="com.YOUR_ORGANIZATION.YOUR_APP_SLUG"
version="1.0"
xmlns="http://www.w3.org/ns/widgets"
xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>Ehattesaht</name>
<description />
<author email="[email protected]"
href="http://mothertongues.org/">Aidan Pine</author>
<content src="index.html" />
...

Bundle ID

First, change the bundle identifiers for all the platforms you wish to build. For Android change android-packageName and for iOS, change ios-CFBundleIdentifer.

important

Pay attention to give your bundle ids appropriate names - you cannot change them once you've published the app. I suggest keeping the identifiers the same for both Android and iOS if possible.

Version

For iOS, you can set the version by changing version, but for Android, you change it by changing android-versionCode.

note

The version attribute accepts decimal values, but the android-versionCode requires 5 digit integers.

Name

Change your app's name by editing the text inside the name element highlighted above.

note

You may have trouble uploading your app if you use certain non-ASCII characters. Frustratingly, the full list of accepted characters does not seem to be published.

Author

You can change your email, website and name as the author by editing the author element highlighted above.

Resources

Ionic will generate all of the different sizes you need for your App's icon and splash screen, but you need to first provide it with templates.

Your source image for icons should be at least 1024x1024 and your splash screen artwork should be 2732x2732. For more information please see Ionic's documentation.

Once you've moved your icon.png and splash.png files to your ~/mothertongues-UI/resources directory, you can generate all of the necessary icon and splash image sizes:

ionic cordova resources

GitHub Pages

There is a built version of the app in mothertongues-ui/www - just replace the mothertongues-ui/www/assets/js/config.js and mothertongues-ui/www/assets/js/dict_cached.js files with your exported data, commit and push the changes, and then turn on GitHub Pages and your app will be published online.

Android (Google Play)

Preliminary steps

Google Play Developer Account

Before publishing your app to the Google Play store, you'll need to register for a Google Play Developer account. At current time of writing, this involves a one-time $25USD fee. The following steps can be done once you've made your account.

Install Android Studio

You must have Android Studio installed, and also ensure your installation includes keytool. You must also have the Android SDK installed that matches your config.xml:

<?xml version='1.0' encoding='utf-8'?>
<widget android-versionCode="10000" id="org.mothertongues.mtdui" version="1.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>mothertongues-UI</name>
<description />
<author email="[email protected]" href="http://mothertongues.org/">Aidan Pine</author>
...
<preference name="android-targetSdkVersion" value="28" />

Testing

I recommend also installing Genymotion to run and test your app.

Building

  1. Create a signing key

You must have keytool. Then, run:

keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
important

You can change my-release-key.keystore to any name. But it is very important to keep this file in a safe spot. If you lose it, or forget the password, you will not be able to update your app!!

  1. Add the platform:

Make sure you're in your dictionary's directory (usually ~/mothertongues-UI) and then:

ionic cordova platform add android

After adding the platform, the cordova plugins that we use automatically register some permissions. Unfortunately, the Media plugin also registers the RECORD_AUDIO and READ_PHONE_STATE permissions, which we don't need for MotherTongues and which give an unnecessary warning to the installer. So, to fix this, go to ~/mothertongues-UI/platforms/android/android.json and remove the highlighted lines below:

"AndroidManifest.xml": {
"parents": {
"/*": [
{
"xml": "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />",
"count": 3
},
{
"xml": "<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />",
"count": 1
},
{
"xml": "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />",
"count": 1
},
{
"xml": "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />",
"count": 1
}
]
}
}
}
},
...

Then, go to ~/mothertongues-UI/platforms/android/app/src/main/AndroidManifest.xml and remove the following highlighted lines:

<?xml version='1.0' encoding='utf-8'?>
<manifest android:hardwareAccelerated="true" android:versionCode="10000" android:versionName="1.0" package="org.mothertongues.mtdui" xmlns:android="http://schemas.android.com/apk/res/android">
<supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:hardwareAccelerated="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true">
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" android:label="@string/activity_name" android:launchMode="singleTop" android:name="MainActivity" android:theme="@android:style/Theme.DeviceDefault.NoActionBar" android:windowSoftInputMode="adjustResize">
<intent-filter android:label="@string/launcher_name">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
</manifest>
  1. Build your android app:
ionic cordova build android --release

Your app will then be built at ~/mothertongues-UI/platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk.

  1. Then, sign your app,
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore "your.keystore" "~/mothertongues-UI/platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk" alias_name
  1. Then, zip-align your app,
zipalign -v 4 "~/mothertongues-UI/platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk" "~/mothertongues-UI/platforms/android/app/build/outputs/apk/release/release.apk"

Then, create a new app and upload your ~/mothertongues-UI/platforms/android/app/build/outputs/apk/release/release.apk file.

Re-building

You must first 'bump' the version in config.xml and follow steps 3, 4, 5 using the keystore you already created.

Uploading to Google Play

First, log in to your Google Play account. Then, create an new application:

new app

There, you will have to fill out information such as the title, description and contact information for your app. You will also have to complete a 'content rating', categorize your app, add screenshots, and choose the countries to allow distribution for.

You will also have to create a new release (the screenshot shows a Beta release for example):

new release

And:

create release

Opt out of Google managing your signing:

opt out

And drag/drop your apk:

opt out

Then, you can upload the ~/mothertongues-UI/platforms/android/app/build/outputs/apk/release/release.apk file

If you've successfully checked off all of the boxes, you can review and 'roll out' your release to production to publish on the Google Play Store after testing it out. The screenshot below shows boxes that have not been checked:

opt out

iOS

Preliminary steps

You must have a Mac do the following build steps on a Mac. Unfortunately, there's no way around this. Both Travis and GitHub Actions let you spin up Mac hardware, so if you don't own a Mac, you might look to adapt these steps to one of those.

Apple Developer license

You must have an Apple Developer License in order to upload an app to the Apple App Store. At time of writing, this is a yearly $99USD fee.

Xcode

Your mac must have Xcode installed. At time of writing, you must also have MacOS 10.15.0 (Catalina) or higher, Xcode 11, and iOS SDK 13 as apps targetting previous SDKs have been disallowed since March 2019.

note

Apple's $99USD/yr fee, constantly changing requirements, and utter lack of support make publishing on iOS a total pain. It's a shame, but they're usually a necessary evil - just remember to take deep breaths as you deal with bugs 🙃

Building with Xcode

  1. Add ios platform

Make sure you're in your dictionary's directory (usually ~/mothertongues-UI) and then:

ionic cordova platform add ios

  1. Build your iOS app:

ionic cordova build ios --release

  1. Open the newly created xcodeproj file at ~/mothertongues-UI/platforms/ios/<YOUR_APP_NAME>.xcodeproj

  2. Change to 'legacy build'

File > Project Settings

legacy
  1. Change plist by adding 'App Uses Non-Exempt Encryption' to NO
plist
  1. Disable Push Notifications*
push notifications

*photo cred: Steven de Salas

  1. Add your Apple Developer account
add account
  1. Creating credentials

Frankly, code signing on iOS is a huge hassle. First, create an App ID, then you must create a 'distribution provisioning profile' in order to provision your app for being downloaded on the App Store. Then, you need to select 'automatic code signing' in Xcode:

auto-sign
  1. Making your app Store page

There are lots of guides on how to do this online. But it's essentially the same as the Android process. You'll need to fill out some meta data and upload screenshots etc.

  1. Select a Generic iOS Device build target
build target
  1. Archive the build
archive
  1. Then, Validate and Distribute your app

You'll need to select 'iOS Store' when distributing, and then 'upload'. This will upload your app to iTunes Connect where you can then test it out on TestFlight or submit it for review to be released.

distribute

Rebuilding

To re-build (after updating data for example), you must do steps 2, 3, 10, 11 and 12 from the build process.

fastlane

As you can see, building can be time consuming, particularly if you have many steps. It's also difficult to keep up with the hardware requirements imposed by Apple. So, you can set up fastlane to automate some of the steps.

There are many ways to use fastlane, this small guide will show you how to set up automatic uploads to TestFlight using GitHub actions.

iOS

  1. Install TestFlight

  2. Initialize Fastlane

cd ~/mothertongues-UI/platforms/ios && fastlane init
  1. Select 2. 👩‍✈️ Automate beta distribution to TestFlight.

Fastlane will add a Gemfile, Gemfile.lock and fastlane folder to your project.

important

You should have already created your provisioning profile and other credentials. See step 7-9 of 'Building with Xcode

  1. Automate code-signing

Replace the step in your fastlane/Fastfile with the following:

platform :ios do
desc "Push a new build to TestFlight"
lane :ci do
# Increment the build number (not the version number)
# Providing the xcodeproj is optional
increment_build_number(xcodeproj: "<YOUR_XCODE_PROJ_NAME>.xcodeproj")
create_keychain(
name: "foobar",
password: ENV["MATCH_KEYCHAIN_PASSWORD"],
default_keychain: true,
unlock: true,
timeout: 3600,
require_create: true,
add_to_search_list: true
)
match(app_identifier: "<YOUR_APP_BUNDLE_ID>", type: "appstore",
git_basic_authorization: Base64.strict_encode64(ENV['GH_PAT']),
keychain_name: "foobar", keychain_password: ENV["MATCH_KEYCHAIN_PASSWORD"])
disable_automatic_code_signing
settings_to_override = {
:BUNDLE_IDENTIFIER => "<YOUR_APP_BUNDLE_ID>",
:PROVISIONING_PROFILE_SPECIFIER => "match AppStore <YOUR_APP_BUNDLE_ID>",
:DEVELOPMENT_TEAM => "D7TR486TEH",
:UseModernBuildSystem => "NO",
}
build_app(workspace: "<YOUR_XCODE_PROJ_NAME>.xcworkspace",
scheme: "<YOUR_XCODE_PROJ_NAME>",
xcargs: settings_to_override,
disable_xcpretty: true,
export_method: 'app-store',
export_options: { provisioningProfiles: { "<YOUR_APP_BUNDLE_ID>" => "match AppStore <YOUR_APP_BUNDLE_ID>" }})
upload_to_app_store(force: true)
end
end
  1. Automate version and build numbers in Xcode

First, change your version and set the versioning system to 'Apple Generic'

versioning

Change the build numbers

build numbers
  1. Set up an automatic code signing routine using match

Match lets you save all of your Apple credentials in a single place (using either git, google_cloud or s3). I recommend using git.

To do that, from your ~/mothertongues-UI/platforms/ios folder, run fastlane match init and select git. Then, give it the address of a private repo you control.

Then, run fastlane match appstore to generate the provisioning profile for your app.

  1. Finally, set it all up with GitHub Actions

Set up a new GitHub action with the following:

# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [master]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: macOS-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/[email protected]
with:
persist-credentials: false
- name: Install node
uses: actions/setup-[email protected]
with:
node-version: 12.15.0
- run: npm install
- run: npm install -g @ionic/cli cordova
- run: ionic cordova build ios --release
# Installation
- name: Gem Install
run: cd $GITHUB_WORKSPACE/platforms/ios && gem install bundler:1.17.2 && bundle install && bundle update --bundler && cd $GITHUB_WORKSPACE
# Runs fastlane
- name: Fastlane Action
uses: maierj/fastlane-[email protected]
env: # Or as an environment variable
MATCH_PASSWORD: ${{ secrets.MATCH_KEYCHAIN_PASSWORD }}
MATCH_KEYCHAIN_NAME: ${{ secrets.MATCH_KEYCHAIN_NAME }}
MATCH_KEYCHAIN_PASSWORD: ${{ secrets.MATCH_KEYCHAIN_PASSWORD }}
GH_PAT: ${{ secrets.GH_PAT }}
FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }}
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
with:
# The lane that should be executed.
lane: ci
# The relative path from the project root directory to the subdirectory where the fastlane folder is located.
subdirectory: ./platforms/ios
# The directory where Ruby gems should be installed to and cached. If a relative path is specified, it's applied relative to the location of the Gemfile, which is either the project root or the directory from the `subdirectory` parameter.
bundle-install-path: .
# The action tracks usage in Firebase by default. You can disable tracking by setting this parameter to 'true'.
skip-tracking: true

As you can see, there is a whole host of GitHub secrets that have to be created. To break these down:

  1. MATCH_PASSWORD

You must provide the password from your match encryption here.

  1. MATCH_KEYCHAIN_NAME

This is the name of the keychain to be created, I recommend using something basic and memorable

  1. MATCH_KEYCHAIN_PASSWORD

You must have a password for the for the keychain

  1. GH_PAT

You must create a GitHub personal access token and save it as a secrets

  1. FASTLANE_SESSION

You must create a fastlane session. You can do that by running the following with your iTunes Connect email:

fastlane spaceauth -u [email protected]

Save the resulting session as a secret.

tip

These sessions expire, so you will have to renew them. If you are managing multiple apps, you might consider creating a GitHub Organization so that you can share all of these secrets across your builds.

  1. FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD

You must also provide an application specific password by first visiting https://appleid.apple.com/account/manage, and generating a new password.

If you set up your repo with all of these secrets and this action, you can push your code updates, and automatically upload to TestFlight!

Web

Building your web version is much less involved than building an app, so if this is all you need, count yourself lucky!

GitHub Pages

If you've forked and cloned the official MotherTongues UI Web repository, then all you need to to is prepare and build your dictionary and then commit and push the changes:

cd ~/mothertongues-UI-Web && git add . && git commit -a -m "new build" && git push

Custom hosting

To host on your own server:

Change the app name and i18nPrefix in ~/mothertongues-UI-Web/projects/mtd/src/environments/environment.prod.ts to your Dictionary name and the directory your dictionary will be hosted at. So, for https://example.com/dictionaries/mydictionary you would put:

export const environment = {
appName: 'My Dictionary',
envName: 'PROD',
production: true,
test: false,
i18nPrefix: '/dictionaries/mydictionary'
...

Then, if your dictionary is being served anywhere other than at the root of your website (ie, if you put anything other than / in the i18nPrefix above) then change the ~/mothertongues--UI-Web/package.json accordingly:

{
"name": "mtd-web-ui",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --open",
"start:prod": "npm run build:prod && npm run server",
"build": "ng build",
"build:prod": "ng build --prod --output-path build --base-href /dictionaries/mydictionary/",
...