Support multiple locales for font language settings.

am: dbcbe1f

* commit 'dbcbe1f426b17242f2c548fb3df5e2b6a659ac50':
  Support multiple locales for font language settings.

Change-Id: Iad60bf1c01f309e70c3b1ad22e28b22afe44b33f
This commit is contained in:
Seigo Nonaka
2016-04-05 06:42:58 +00:00
committed by android-build-merger
7 changed files with 197 additions and 98 deletions

View File

@@ -177,9 +177,15 @@ uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs, FontFamily*
}
if (vs == 0xFE0F || vs == 0xFE0E) {
// TODO use all language in the list.
const FontLanguage lang = FontLanguageListCache::getById(fontFamily->langId())[0];
const bool hasEmojiFlag = lang.hasEmojiFlag();
const FontLanguages& langs = FontLanguageListCache::getById(fontFamily->langId());
bool hasEmojiFlag = false;
for (size_t i = 0; i < langs.size(); ++i) {
if (langs[i].hasEmojiFlag()) {
hasEmojiFlag = true;
break;
}
}
if (vs == 0xFE0F) {
return hasEmojiFlag ? 2 : 1;
} else { // vs == 0xFE0E
@@ -208,13 +214,12 @@ uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs, FontFamily*
uint32_t FontCollection::calcLanguageMatchingScore(
uint32_t userLangListId, const FontFamily& fontFamily) {
const FontLanguages& langList = FontLanguageListCache::getById(userLangListId);
// TODO use all language in the list.
FontLanguage fontLanguage = FontLanguageListCache::getById(fontFamily.langId())[0];
const FontLanguages& fontLanguages = FontLanguageListCache::getById(fontFamily.langId());
const size_t maxCompareNum = std::min(langList.size(), FONT_LANGUAGES_LIMIT);
uint32_t score = fontLanguage.getScoreFor(langList[0]); // maxCompareNum can't be zero.
for (size_t i = 1; i < maxCompareNum; ++i) {
score = score * 3u + fontLanguage.getScoreFor(langList[i]);
uint32_t score = 0;
for (size_t i = 0; i < maxCompareNum; ++i) {
score = score * 3u + langList[i].calcScoreFor(fontLanguages);
}
return score;
}

View File

@@ -126,24 +126,59 @@ bool FontLanguage::isEqualScript(const FontLanguage& other) const {
return other.mScript == mScript;
}
bool FontLanguage::supportsScript(uint8_t requestedBits) const {
return requestedBits != 0 && (mSubScriptBits & requestedBits) == requestedBits;
// static
bool FontLanguage::supportsScript(uint8_t providedBits, uint8_t requestedBits) {
return requestedBits != 0 && (providedBits & requestedBits) == requestedBits;
}
bool FontLanguage::supportsHbScript(hb_script_t script) const {
static_assert(SCRIPT_TAG('J', 'p', 'a', 'n') == HB_TAG('J', 'p', 'a', 'n'),
"The Minikin script and HarfBuzz hb_script_t have different encodings.");
if (script == mScript) return true;
return supportsScript(scriptToSubScriptBits(script));
return supportsScript(mSubScriptBits, scriptToSubScriptBits(script));
}
int FontLanguage::getScoreFor(const FontLanguage other) const {
if (isUnsupported() || other.isUnsupported()) {
return 0;
} else if (isEqualScript(other) || supportsScript(other.mSubScriptBits)) {
return mLanguage == other.mLanguage ? 2 : 1;
} else {
return 0;
int FontLanguage::calcScoreFor(const FontLanguages& supported) const {
int score = 0;
for (size_t i = 0; i < supported.size(); ++i) {
if (isEqualScript(supported[i]) ||
supportsScript(supported[i].mSubScriptBits, mSubScriptBits)) {
if (mLanguage == supported[i].mLanguage) {
return 2;
} else {
score = 1;
}
}
}
if (score == 1) {
return score;
}
if (supportsScript(supported.getUnionOfSubScriptBits(), mSubScriptBits)) {
// Gives score of 2 only if the language matches all of the font languages except for the
// exact match case handled above.
return (mLanguage == supported[0].mLanguage && supported.isAllTheSameLanguage()) ? 2 : 1;
}
return 0;
}
FontLanguages::FontLanguages(std::vector<FontLanguage>&& languages)
: mLanguages(std::move(languages)) {
if (mLanguages.empty()) {
return;
}
const FontLanguage& lang = mLanguages[0];
mIsAllTheSameLanguage = true;
mUnionOfSubScriptBits = lang.mSubScriptBits;
for (size_t i = 1; i < mLanguages.size(); ++i) {
mUnionOfSubScriptBits |= mLanguages[i].mSubScriptBits;
if (mIsAllTheSameLanguage && lang.mLanguage != mLanguages[i].mLanguage) {
mIsAllTheSameLanguage = false;
}
}
}

View File

@@ -24,6 +24,11 @@
namespace android {
// Due to the limits in font fallback score calculation, we can't use anything more than 17
// languages.
const size_t FONT_LANGUAGES_LIMIT = 17;
class FontLanguages;
// FontLanguage is a compact representation of a BCP 47 language tag. It
// does not capture all possible information, only what directly affects
// font rendering.
@@ -54,12 +59,16 @@ public:
std::string getString() const;
// Calculates a matching score. This score represents how well the input languages cover this
// language. The maximum score in the language list is returned.
// 0 = no match, 1 = script match, 2 = script and primary language match.
int getScoreFor(const FontLanguage other) const;
int calcScoreFor(const FontLanguages& supported) const;
uint64_t getIdentifier() const { return (uint64_t)mScript << 32 | (uint64_t)mLanguage; }
private:
friend class FontLanguages; // for FontLanguages constructor
// ISO 15924 compliant script code. The 4 chars script code are packed into a 32 bit integer.
uint32_t mScript;
@@ -80,12 +89,37 @@ private:
uint8_t mSubScriptBits;
static uint8_t scriptToSubScriptBits(uint32_t script);
bool supportsScript(uint8_t requestedBits) const;
// Returns true if the provide subscript bits has the requested subscript bits.
// Note that this function returns false if the requested subscript bits are empty.
static bool supportsScript(uint8_t providedBits, uint8_t requestedBits);
};
// Due to the limit of font fallback cost calculation, we can't use anything more than 17 languages.
const size_t FONT_LANGUAGES_LIMIT = 17;
typedef std::vector<FontLanguage> FontLanguages;
// An immutable list of languages.
class FontLanguages {
public:
FontLanguages(std::vector<FontLanguage>&& languages);
FontLanguages() : mUnionOfSubScriptBits(0), mIsAllTheSameLanguage(false) {}
FontLanguages(FontLanguages&&) = default;
size_t size() const { return mLanguages.size(); }
bool empty() const { return mLanguages.empty(); }
const FontLanguage& operator[] (size_t n) const { return mLanguages[n]; }
private:
friend struct FontLanguage; // for calcScoreFor
std::vector<FontLanguage> mLanguages;
uint8_t mUnionOfSubScriptBits;
bool mIsAllTheSameLanguage;
uint8_t getUnionOfSubScriptBits() const { return mUnionOfSubScriptBits; }
bool isAllTheSameLanguage() const { return mIsAllTheSameLanguage; }
// Do not copy and assign.
FontLanguages(const FontLanguages&) = delete;
void operator=(const FontLanguages&) = delete;
};
} // namespace android

View File

@@ -76,8 +76,8 @@ static size_t toLanguageTag(char* output, size_t outSize, const std::string& loc
return outLength;
}
static FontLanguages constructFontLanguages(const std::string& input) {
FontLanguages result;
static std::vector<FontLanguage> parseLanguageList(const std::string& input) {
std::vector<FontLanguage> result;
size_t currentIdx = 0;
size_t commaLoc = 0;
char langTag[ULOC_FULLNAME_CAPACITY];
@@ -121,11 +121,11 @@ uint32_t FontLanguageListCache::getId(const std::string& languages) {
// Given language list is not in cache. Insert it and return newly assigned ID.
const uint32_t nextId = inst->mLanguageLists.size();
FontLanguages fontLanguages = constructFontLanguages(languages);
FontLanguages fontLanguages(parseLanguageList(languages));
if (fontLanguages.empty()) {
return kEmptyListId;
}
inst->mLanguageLists.push_back(fontLanguages);
inst->mLanguageLists.push_back(std::move(fontLanguages));
inst->mLanguageListLookupTable.insert(std::make_pair(languages, nextId));
return nextId;
}
@@ -146,7 +146,7 @@ FontLanguageListCache* FontLanguageListCache::getInstance() {
// Insert an empty language list for mapping default language list to kEmptyListId.
// The default language list has only one FontLanguage and it is the unsupported language.
instance->mLanguageLists.push_back(FontLanguages({FontLanguage()}));
instance->mLanguageLists.push_back(FontLanguages());
instance->mLanguageListLookupTable.insert(std::make_pair("", kEmptyListId));
}
return instance;

View File

@@ -698,72 +698,104 @@ TEST_F(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) {
TEST_F(FontCollectionItemizeTest, itemize_LanguageScore) {
struct TestCase {
std::string userPreferredLanguages;
std::string fontLanguages;
std::vector<std::string> fontLanguages;
int selectedFontIndex;
} testCases[] = {
// Font can specify empty language.
{ "und", { "", "" }, 0 },
{ "und", { "", "en-Latn" }, 0 },
{ "en-Latn", { "", "" }, 0 },
{ "en-Latn", { "", "en-Latn" }, 1 },
// Single user preferred language.
// Exact match case
{ "en-Latn", "en-Latn,ja-Jpan", 0 },
{ "ja-Jpan", "en-Latn,ja-Jpan", 1 },
{ "en-Latn", "en-Latn,nl-Latn,es-Latn", 0 },
{ "nl-Latn", "en-Latn,nl-Latn,es-Latn", 1 },
{ "es-Latn", "en-Latn,nl-Latn,es-Latn", 2 },
{ "es-Latn", "en-Latn,en-Latn,nl-Latn", 0 },
{ "en-Latn", { "en-Latn", "ja-Jpan" }, 0 },
{ "ja-Jpan", { "en-Latn", "ja-Jpan" }, 1 },
{ "en-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 0 },
{ "nl-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 1 },
{ "es-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 2 },
{ "es-Latn", { "en-Latn", "en-Latn", "nl-Latn" }, 0 },
// Exact script match case
{ "en-Latn", "nl-Latn,be-Latn", 0 },
{ "en-Arab", "nl-Latn,ar-Arab", 1 },
{ "en-Latn", "be-Latn,ar-Arab,bd-Beng", 0 },
{ "en-Arab", "be-Latn,ar-Arab,bd-Beng", 1 },
{ "en-Beng", "be-Latn,ar-Arab,bd-Beng", 2 },
{ "en-Beng", "be-Latn,ar-Beng,bd-Beng", 1 },
{ "zh-Hant", "zh-Hant,zh-Hans", 0 },
{ "zh-Hans", "zh-Hant,zh-Hans", 1 },
{ "en-Latn", { "nl-Latn", "e-Latn" }, 0 },
{ "en-Arab", { "nl-Latn", "ar-Arab" }, 1 },
{ "en-Latn", { "be-Latn", "ar-Arab", "d-Beng" }, 0 },
{ "en-Arab", { "be-Latn", "ar-Arab", "d-Beng" }, 1 },
{ "en-Beng", { "be-Latn", "ar-Arab", "d-Beng" }, 2 },
{ "en-Beng", { "be-Latn", "ar-Beng", "d-Beng" }, 1 },
{ "zh-Hant", { "zh-Hant", "zh-Hans" }, 0 },
{ "zh-Hans", { "zh-Hant", "zh-Hans" }, 1 },
// Subscript match case, e.g. Jpan supports Hira.
{ "en-Hira", "ja-Jpan", 0 },
{ "zh-Hani", "zh-Hans,zh-Hant", 0 },
{ "zh-Hani", "zh-Hant,zh-Hans", 0 },
{ "en-Hira", "zh-Hant,ja-Jpan,ja-Jpan", 1 },
{ "en-Hira", { "ja-Jpan" }, 0 },
{ "zh-Hani", { "zh-Hans", "zh-Hant" }, 0 },
{ "zh-Hani", { "zh-Hant", "zh-Hans" }, 0 },
{ "en-Hira", { "zh-Hant", "ja-Jpan", "ja-Jpan" }, 1 },
// Language match case
{ "ja-Latn", "zh-Latn,ja-Latn", 1 },
{ "zh-Latn", "zh-Latn,ja-Latn", 0 },
{ "ja-Latn", "zh-Latn,ja-Latn", 1 },
{ "ja-Latn", "zh-Latn,ja-Latn,ja-Latn", 1 },
{ "ja-Latn", { "zh-Latn", "ja-Latn" }, 1 },
{ "zh-Latn", { "zh-Latn", "ja-Latn" }, 0 },
{ "ja-Latn", { "zh-Latn", "ja-Latn" }, 1 },
{ "ja-Latn", { "zh-Latn", "ja-Latn", "ja-Latn" }, 1 },
// Mixed case
// Script/subscript match is strongest.
{ "ja-Jpan", "en-Latn,ja-Latn,en-Jpan", 2 },
{ "ja-Hira", "en-Latn,ja-Latn,en-Jpan", 2 },
{ "ja-Hira", "en-Latn,ja-Latn,en-Jpan,en-Jpan", 2 },
{ "ja-Jpan", { "en-Latn", "ja-Latn", "en-Jpan" }, 2 },
{ "ja-Hira", { "en-Latn", "ja-Latn", "en-Jpan" }, 2 },
{ "ja-Hira", { "en-Latn", "ja-Latn", "en-Jpan", "en-Jpan" }, 2 },
// Language match only happens if the script matches.
{ "ja-Hira", "en-Latn,ja-Latn", 0 },
{ "ja-Hira", "en-Jpan,ja-Jpan", 1 },
{ "ja-Hira", { "en-Latn", "ja-Latn" }, 0 },
{ "ja-Hira", { "en-Jpan", "ja-Jpan" }, 1 },
// Multiple languages.
// Even if all fonts have the same score, use the 2nd language for better selection.
{ "en-Latn,ja-Jpan", "zh-Hant,zh-Hans,ja-Jpan", 2 },
{ "en-Latn,nl-Latn", "es-Latn,be-Latn,nl-Latn", 2 },
{ "en-Latn,br-Latn,nl-Latn", "es-Latn,be-Latn,nl-Latn", 2 },
{ "en-Latn,br-Latn,nl-Latn", "es-Latn,be-Latn,nl-Latn,nl-Latn", 2 },
{ "en-Latn,ja-Jpan", { "zh-Hant", "zh-Hans", "ja-Jpan" }, 2 },
{ "en-Latn,nl-Latn", { "es-Latn", "be-Latn", "nl-Latn" }, 2 },
{ "en-Latn,br-Latn,nl-Latn", { "es-Latn", "be-Latn", "nl-Latn" }, 2 },
{ "en-Latn,br-Latn,nl-Latn", { "es-Latn", "be-Latn", "nl-Latn", "nl-Latn" }, 2 },
// Script score.
{ "en-Latn,ja-Jpan", "en-Arab,en-Jpan", 1 },
{ "en-Latn,ja-Jpan", "en-Arab,en-Jpan,en-Jpan", 1 },
{ "en-Latn,ja-Jpan", { "en-Arab", "en-Jpan" }, 1 },
{ "en-Latn,ja-Jpan", { "en-Arab", "en-Jpan", "en-Jpan" }, 1 },
// Language match case
{ "en-Latn,ja-Latn", "bd-Latn,ja-Latn", 1 },
{ "en-Latn,ja-Latn", "bd-Latn,ja-Latn,ja-Latn", 1 },
{ "en-Latn,ja-Latn", { "bd-Latn", "ja-Latn" }, 1 },
{ "en-Latn,ja-Latn", { "bd-Latn", "ja-Latn", "ja-Latn" }, 1 },
// Language match only happens if the script matches.
{ "en-Latn,ar-Arab", "en-Beng,ar-Arab", 1 },
{ "en-Latn,ar-Arab", { "en-Beng", "ar-Arab" }, 1 },
// Multiple languages in the font settings.
{ "ko-Jamo", { "ja-Jpan", "ko-Kore", "ko-Kore,ko-Jamo"}, 2 },
{ "en-Latn", { "ja-Jpan", "en-Latn,ja-Jpan"}, 1 },
{ "en-Latn", { "ja-Jpan", "ja-Jpan,en-Latn"}, 1 },
{ "en-Latn", { "ja-Jpan,zh-Hant", "en-Latn,ja-Jpan", "en-Latn"}, 1 },
{ "en-Latn", { "zh-Hant,ja-Jpan", "ja-Jpan,en-Latn", "en-Latn"}, 1 },
// Kore = Hang + Hani, etc.
{ "ko-Kore", { "ko-Hang", "ko-Jamo,ko-Hani", "ko-Hang,ko-Hani"}, 2 },
{ "ja-Hrkt", { "ja-Hira", "ja-Kana", "ja-Hira,ja-Kana"}, 2 },
{ "ja-Jpan", { "ja-Hira", "ja-Kana", "ja-Hani", "ja-Hira,ja-Kana,ja-Hani"}, 3 },
{ "zh-Hanb", { "zh-Hant", "zh-Bopo", "zh-Hant,zh-Bopo"}, 2 },
{ "zh-Hanb", { "ja-Hanb", "zh-Hant,zh-Bopo"}, 1 },
// Language match with unified subscript bits.
{ "zh-Hanb", { "zh-Hant", "zh-Bopo", "ja-Hant,ja-Bopo", "zh-Hant,zh-Bopo"}, 3 },
{ "zh-Hanb", { "zh-Hant", "zh-Bopo", "ja-Hant,zh-Bopo", "zh-Hant,zh-Bopo"}, 3 },
};
for (auto testCase : testCases) {
std::string fontLanguagesStr = "{";
for (size_t i = 0; i < testCase.fontLanguages.size(); ++i) {
if (i != 0) {
fontLanguagesStr += ", ";
}
fontLanguagesStr += "\"" + testCase.fontLanguages[i] + "\"";
}
fontLanguagesStr += "}";
SCOPED_TRACE("Test of user preferred languages: \"" + testCase.userPreferredLanguages +
"\" with font languages: " + testCase.fontLanguages);
"\" with font languages: " + fontLanguagesStr);
std::vector<FontFamily*> families;
@@ -778,12 +810,10 @@ TEST_F(FontCollectionItemizeTest, itemize_LanguageScore) {
// Each font family is associated with a specified language. All font families except for
// the first font support U+9AA8.
std::unordered_map<MinikinFont*, int> fontLangIdxMap;
const FontLanguages& fontLanguages = registerAndGetFontLanguages(testCase.fontLanguages);
for (size_t i = 0; i < fontLanguages.size(); ++i) {
const FontLanguage& fontLanguage = fontLanguages[i];
for (size_t i = 0; i < testCase.fontLanguages.size(); ++i) {
FontFamily* family = new FontFamily(
FontStyle::registerLanguageList(fontLanguage.getString()), 0 /* variant */);
FontStyle::registerLanguageList(testCase.fontLanguages[i]), 0 /* variant */);
MinikinFont* minikin_font = new MinikinFontForTest(kJAFont);
family->addFont(minikin_font);
families.push_back(family);

View File

@@ -30,7 +30,7 @@ namespace android {
typedef ICUTestBase FontLanguagesTest;
typedef ICUTestBase FontLanguageTest;
static FontLanguages createFontLanguages(const std::string& input) {
static const FontLanguages& createFontLanguages(const std::string& input) {
AutoMutex _l(gMinikinLock);
uint32_t langId = FontLanguageListCache::getId(input);
return FontLanguageListCache::getById(langId);
@@ -217,35 +217,30 @@ TEST_F(FontLanguagesTest, basicTests) {
EXPECT_EQ(0u, emptyLangs.size());
FontLanguage english = createFontLanguage("en");
FontLanguages singletonLangs = createFontLanguages("en");
const FontLanguages& singletonLangs = createFontLanguages("en");
EXPECT_EQ(1u, singletonLangs.size());
EXPECT_EQ(english, singletonLangs[0]);
FontLanguage french = createFontLanguage("fr");
FontLanguages twoLangs = createFontLanguages("en,fr");
const FontLanguages& twoLangs = createFontLanguages("en,fr");
EXPECT_EQ(2u, twoLangs.size());
EXPECT_EQ(english, twoLangs[0]);
EXPECT_EQ(french, twoLangs[1]);
}
TEST_F(FontLanguagesTest, unsupportedLanguageTests) {
FontLanguage unsupportedLang = createFontLanguage("abcd");
ASSERT_TRUE(unsupportedLang.isUnsupported());
const FontLanguages& oneUnsupported = createFontLanguages("abcd-example");
EXPECT_TRUE(oneUnsupported.empty());
FontLanguages oneUnsupported = createFontLanguages("abcd-example");
EXPECT_EQ(1u, oneUnsupported.size());
EXPECT_TRUE(oneUnsupported[0].isUnsupported());
FontLanguages twoUnsupporteds = createFontLanguages("abcd-example,abcd-example");
EXPECT_EQ(1u, twoUnsupporteds.size());
EXPECT_TRUE(twoUnsupporteds[0].isUnsupported());
const FontLanguages& twoUnsupporteds = createFontLanguages("abcd-example,abcd-example");
EXPECT_TRUE(twoUnsupporteds.empty());
FontLanguage english = createFontLanguage("en");
FontLanguages firstUnsupported = createFontLanguages("abcd-example,en");
const FontLanguages& firstUnsupported = createFontLanguages("abcd-example,en");
EXPECT_EQ(1u, firstUnsupported.size());
EXPECT_EQ(english, firstUnsupported[0]);
FontLanguages lastUnsupported = createFontLanguages("en,abcd-example");
const FontLanguages& lastUnsupported = createFontLanguages("en,abcd-example");
EXPECT_EQ(1u, lastUnsupported.size());
EXPECT_EQ(english, lastUnsupported[0]);
}
@@ -256,20 +251,20 @@ TEST_F(FontLanguagesTest, repeatedLanguageTests) {
FontLanguage englishInLatn = createFontLanguage("en-Latn");
ASSERT_TRUE(english == englishInLatn);
FontLanguages langs = createFontLanguages("en,en-Latn");
const FontLanguages& langs = createFontLanguages("en,en-Latn");
EXPECT_EQ(1u, langs.size());
EXPECT_EQ(english, langs[0]);
// Country codes are ignored.
FontLanguages fr = createFontLanguages("fr,fr-CA,fr-FR");
const FontLanguages& fr = createFontLanguages("fr,fr-CA,fr-FR");
EXPECT_EQ(1u, fr.size());
EXPECT_EQ(french, fr[0]);
// The order should be kept.
langs = createFontLanguages("en,fr,en-Latn");
EXPECT_EQ(2u, langs.size());
EXPECT_EQ(english, langs[0]);
EXPECT_EQ(french, langs[1]);
const FontLanguages& langs2 = createFontLanguages("en,fr,en-Latn");
EXPECT_EQ(2u, langs2.size());
EXPECT_EQ(english, langs2[0]);
EXPECT_EQ(french, langs2[1]);
}
TEST_F(FontLanguagesTest, undEmojiTests) {

View File

@@ -56,18 +56,18 @@ TEST_F(FontLanguageListCacheTest, getById) {
FontLanguage english = FontLanguageListCache::getById(enLangId)[0];
FontLanguage japanese = FontLanguageListCache::getById(jpLangId)[0];
FontLanguages defLangs = FontLanguageListCache::getById(0);
EXPECT_EQ(1UL, defLangs.size());
EXPECT_TRUE(defLangs[0].isUnsupported());
const FontLanguages& defLangs = FontLanguageListCache::getById(0);
EXPECT_TRUE(defLangs.empty());
FontLanguages langs = FontLanguageListCache::getById(FontLanguageListCache::getId("en"));
const FontLanguages& langs = FontLanguageListCache::getById(FontLanguageListCache::getId("en"));
ASSERT_EQ(1UL, langs.size());
EXPECT_EQ(english, langs[0]);
langs = FontLanguageListCache::getById(FontLanguageListCache::getId("en,jp"));
ASSERT_EQ(2UL, langs.size());
EXPECT_EQ(english, langs[0]);
EXPECT_EQ(japanese, langs[1]);
const FontLanguages& langs2 =
FontLanguageListCache::getById(FontLanguageListCache::getId("en,jp"));
ASSERT_EQ(2UL, langs2.size());
EXPECT_EQ(english, langs2[0]);
EXPECT_EQ(japanese, langs2[1]);
}
} // android