org.chromium.ChromeI18n.java Source code

Java tutorial

Introduction

Here is the source code for org.chromium.ChromeI18n.java

Source

// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;

import com.squareup.okhttp.internal.Base64;

public class ChromeI18n extends CordovaPlugin implements ChromeExtensionURLs.RequestModifyInterface {

    private static final String LOG_TAG = "ChromeI18n";
    // Save the locale chain so we don't have to recalculate each time
    private List<String> chosenLocales;
    // Save any retrieved message.json contents in memory so that we don't have to retrieve it again
    private Map<String, JSONObject> memoizedJsonContents = new HashMap<String, JSONObject>();
    // The pattern of any messages we need to replace
    private Pattern patterRegex = Pattern.compile("__MSG_(@@)?[a-zA-Z0-9_-]*__");

    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
        super.initialize(cordova, webView);
        PluginManager pm = null;
        try {
            Method gpm = webView.getClass().getMethod("getPluginManager");
            pm = (PluginManager) gpm.invoke(webView);
        } catch (Exception e) {
        }
        if (pm == null) {
            try {
                Field pmf = webView.getClass().getField("pluginManager");
                pm = (PluginManager) pmf.get(webView);
            } catch (Exception e) {
            }
        }
        ChromeExtensionURLs extPlugin = (ChromeExtensionURLs) pm.getPlugin("ChromeExtensionURLs");
        extPlugin.i18nPlugin = this;
    }

    @Override
    public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext)
            throws JSONException {
        if ("getAcceptLanguages".equals(action)) {
            getAcceptLanguages(args, callbackContext);
            return true;
        }

        return false;
    }

    private void getAcceptLanguages(final CordovaArgs args, final CallbackContext callbackContext) {
        try {
            JSONArray ret = new JSONArray();
            Locale locale = Locale.getDefault();
            String localString = locale.toString().replace('_', '-');
            ret.put(localString);
            callbackContext.success(ret);
        } catch (Exception e) {
            callbackContext.error("Could not retrieve supported locales");
            Log.e(LOG_TAG, "Could not retrieve supported locales", e);
        }
    }

    @Override
    public Uri remapChromeUri(Uri uri) {
        Uri ret = Uri.parse(replacePatternsInLine(uri.toString()));
        if (ret.getPath().endsWith(".css") || uri.getPath().equals("manifest.json")) {
            Uri fileUri = Uri.parse("file:///android_asset/www" + uri.getPath());
            fileUri = webView.getResourceApi().remapUri(fileUri);
            try {
                OpenForReadResult readResult = webView.getResourceApi().openForRead(fileUri, true);
                try {
                    byte[] newData = replaceI18nPlaceholders(readResult.inputStream);
                    return Uri.parse("data:text/css;charset=utf-8;base64," + Base64.encode(newData));
                } finally {
                    if (readResult != null) {
                        readResult.inputStream.close();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    private byte[] replaceI18nPlaceholders(InputStream is) throws IOException {
        // Process the input stream line by line
        // Default byte array size is 32 bytes. Use something more reasonable for web resources - 32K
        int defaultByteArraySize = 32768;
        String ls = System.getProperty("line.separator");
        ByteArrayOutputStream os = new ByteArrayOutputStream(defaultByteArraySize);
        String line = null;
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        while ((line = reader.readLine()) != null) {
            line = replacePatternsInLine(line);
            // Note the last line might get an extra \n pushed in. This shouldn't affect the loading of web resources
            os.write((line + ls).getBytes());
        }
        return os.toByteArray();
    }

    private String replacePatternsInLine(String line) {
        try {
            Matcher matcher = patterRegex.matcher(line);
            StringBuilder constructedLine = new StringBuilder();
            int currStart = 0;

            while (currStart < line.length() && matcher.find(currStart)) {
                String preString = line.substring(currStart, matcher.start());
                String match = matcher.group();

                constructedLine.append(preString);
                constructedLine.append(getReplacement(match));
                currStart = matcher.end();
            }
            //get the remaining piece
            constructedLine.append(line.substring(currStart));

            return constructedLine.toString();
        } catch (Exception e) {
            Log.e(LOG_TAG, "An error occurred during the i18n of line : " + line, e);
            // Return the line itself as it is a more graceful fallback than return nothing
            return line;
        }
    }

    @SuppressLint("NewApi")
    private static boolean isRtlLocale(Locale l) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
            return View.LAYOUT_DIRECTION_RTL == TextUtils.getLayoutDirectionFromLocale(l);
        }
        String lang = l.getLanguage().toLowerCase(Locale.ENGLISH);
        return (lang.equals("ar") || lang.equals("fa") || lang.equals("ur") || lang.equals("ps")
                || lang.equals("syr") || lang.equals("dv") || lang.equals("he") || lang.equals("yi"));
    }

    private String getReplacement(String match) {
        // get the message from __MSG_messagename__
        String messageName = match.substring(6, match.length() - 2).toLowerCase(Locale.ENGLISH);
        if (messageName.startsWith("@@")) {
            Locale locale = Locale.getDefault();
            if ("@@extension_id".equals(messageName)) {
                return "{appId}";
            } else if ("@@ui_locale".equals(messageName)) {
                return locale.toString();
            } else if ("@@bidi_dir".equals(messageName)) {
                return isRtlLocale(locale) ? "rtl" : "ltr";
            } else if ("@@bidi_reversed_dir".equals(messageName)) {
                return isRtlLocale(locale) ? "ltr" : "rtl";
            } else if ("@@bidi_start_edge".equals(messageName)) {
                return isRtlLocale(locale) ? "right" : "left";
            } else if ("@@bidi_end_edge".equals(messageName)) {
                return isRtlLocale(locale) ? "left" : "right";
            }
        }

        // Look for replacement in messages.json files
        List<String> localeChain = getLocalesToUse();
        JSONObject messageObject = getMessageFromMessageJson(messageName, localeChain);
        if (messageObject != null) {
            String ret = messageObject.optString("message");
            if (ret != null) {
                return ret;
            }
        }
        // Didn't find a match, just return string as is
        return match;
    }

    private List<String> getLocalesToUse() {
        try {
            if (chosenLocales == null) {
                List<String> localesToUse = new ArrayList<String>();
                String windowLocale = Locale.getDefault().toString().toLowerCase().replace('-', '_');
                localesToUse.add(windowLocale);

                // Construct fallback chain
                int lastIndex;
                while ((lastIndex = windowLocale.lastIndexOf('_')) != -1) {
                    windowLocale = windowLocale.substring(0, lastIndex);
                    localesToUse.add(windowLocale);
                }
                String defaultLocale = getDefaultLocale();
                if (!localesToUse.contains(defaultLocale)) {
                    localesToUse.add(defaultLocale);
                }

                chosenLocales = new ArrayList<String>();
                for (int i = 0; i < localesToUse.size(); i++) {
                    String currentLocale = localesToUse.get(i);
                    if (isLocaleAvailable(currentLocale)) {
                        chosenLocales.add(currentLocale);
                    }
                }
            }
        } catch (Exception e) {
            Log.e(LOG_TAG, "Error occurred while retrieving usable locales", e);
        }
        return chosenLocales;
    }

    private JSONObject getMessageFromMessageJson(String messageName, List<String> localeChain) {
        try {
            for (int i = 0; i < localeChain.size(); i++) {
                String locale = localeChain.get(i);
                if (memoizedJsonContents.get(locale) == null) {
                    String fileName = "www/locales/" + locale + "/messages.json";
                    JSONObject contents = getAssetContents(fileName);
                    // convert all fields to lower case to check case insensitively
                    contents = toLowerCaseMessage(contents);
                    memoizedJsonContents.put(locale, contents);
                }
                JSONObject ret = memoizedJsonContents.get(locale).optJSONObject(messageName);
                if (ret != null) {
                    return ret;
                }
            }
        } catch (Exception e) {
            Log.e(LOG_TAG, "Error occurred while reading through the messages.json files", e);
        }
        return null;
    }

    private static List<String> toStringList(JSONArray array) throws JSONException {
        if (array == null) {
            return null;
        }
        List<String> list = new ArrayList<String>();

        for (int i = 0, l = array.length(); i < l; i++) {
            list.add(array.get(i).toString());
        }

        return list;
    }

    private JSONObject toLowerCaseMessage(JSONObject contents) throws JSONException {
        List<String> messages = toStringList(contents.names());
        for (String message : messages) {
            JSONObject value = contents.getJSONObject(message);
            contents.remove(message);
            contents.put(message.toLowerCase(), value);
        }
        return contents;
    }

    private boolean isLocaleAvailable(String locale) throws IOException {
        List<String> availableLocales = new ArrayList<String>();
        AssetManager am = this.cordova.getActivity().getAssets();
        String[] localesArr = am.list("www/locales");
        for (String currLocale : localesArr) {
            try {
                // Check that the manifest.json exists
                InputStream is = am.open("www/locales/" + currLocale + "/messages.json");
                is.close();
                availableLocales.add(currLocale);
            } catch (IOException e) {
                /* Suppress not found exceptions */ }
        }
        return availableLocales.contains(locale);
    }

    private String getDefaultLocale() throws JSONException, IOException {
        JSONObject manifestContents = getAssetContents("www/manifest.json");
        String defaultLocale = manifestContents.optString("default_locale");
        if (defaultLocale == null) {
            throw new IllegalArgumentException("Default Locale not defined");
        }
        return defaultLocale;
    }

    private JSONObject getAssetContents(String assetName) throws IOException, JSONException {
        Context context = this.cordova.getActivity();
        InputStream is = context.getAssets().open(assetName);
        //Small trick to get the scanner to pull the entire input stream in one go
        Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
        String contents = s.hasNext() ? s.next() : "";
        return new JSONObject(contents);
    }
}