Merge "Pseudolocalizer improvements."

This commit is contained in:
Elliott Hughes
2014-04-18 22:32:53 +00:00
committed by Gerrit Code Review
9 changed files with 337 additions and 106 deletions

View File

@@ -41,6 +41,15 @@ typedef enum Command {
kCommandSingleCrunch, kCommandSingleCrunch,
} Command; } Command;
/*
* Pseudolocalization methods
*/
typedef enum PseudolocalizationMethod {
NO_PSEUDOLOCALIZATION = 0,
PSEUDO_ACCENTED,
PSEUDO_BIDI,
} PseudolocalizationMethod;
/* /*
* Bundle of goodies, including everything specified on the command line. * Bundle of goodies, including everything specified on the command line.
*/ */
@@ -50,7 +59,7 @@ public:
: mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false), : mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false),
mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false), mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false),
mUpdate(false), mExtending(false), mUpdate(false), mExtending(false),
mRequireLocalization(false), mPseudolocalize(false), mRequireLocalization(false), mPseudolocalize(NO_PSEUDOLOCALIZATION),
mWantUTF16(false), mValues(false), mWantUTF16(false), mValues(false),
mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL), mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL),
mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL), mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL),
@@ -94,8 +103,8 @@ public:
void setExtending(bool val) { mExtending = val; } void setExtending(bool val) { mExtending = val; }
bool getRequireLocalization(void) const { return mRequireLocalization; } bool getRequireLocalization(void) const { return mRequireLocalization; }
void setRequireLocalization(bool val) { mRequireLocalization = val; } void setRequireLocalization(bool val) { mRequireLocalization = val; }
bool getPseudolocalize(void) const { return mPseudolocalize; } short getPseudolocalize(void) const { return mPseudolocalize; }
void setPseudolocalize(bool val) { mPseudolocalize = val; } void setPseudolocalize(short val) { mPseudolocalize = val; }
void setWantUTF16(bool val) { mWantUTF16 = val; } void setWantUTF16(bool val) { mWantUTF16 = val; }
bool getValues(void) const { return mValues; } bool getValues(void) const { return mValues; }
void setValues(bool val) { mValues = val; } void setValues(bool val) { mValues = val; }
@@ -250,7 +259,7 @@ private:
bool mUpdate; bool mUpdate;
bool mExtending; bool mExtending;
bool mRequireLocalization; bool mRequireLocalization;
bool mPseudolocalize; short mPseudolocalize;
bool mWantUTF16; bool mWantUTF16;
bool mValues; bool mValues;
int mCompressionMethod; int mCompressionMethod;

View File

@@ -1885,14 +1885,17 @@ int doPackage(Bundle* bundle)
FILE* fp; FILE* fp;
String8 dependencyFile; String8 dependencyFile;
// -c zz_ZZ means do pseudolocalization // -c en_XA or/and ar_XB means do pseudolocalization
ResourceFilter filter; ResourceFilter filter;
err = filter.parse(bundle->getConfigurations()); err = filter.parse(bundle->getConfigurations());
if (err != NO_ERROR) { if (err != NO_ERROR) {
goto bail; goto bail;
} }
if (filter.containsPseudo()) { if (filter.containsPseudo()) {
bundle->setPseudolocalize(true); bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
}
if (filter.containsPseudoBidi()) {
bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
} }
N = bundle->getFileSpecCount(); N = bundle->getFileSpecCount();

View File

@@ -24,8 +24,10 @@ ResourceFilter::parse(const char* arg)
String8 part(p, q-p); String8 part(p, q-p);
if (part == "zz_ZZ") { if (part == "en_XA") {
mContainsPseudo = true; mContainsPseudoAccented = true;
} else if (part == "ar_XB") {
mContainsPseudoBidi = true;
} }
int axis; int axis;
AxisValue value; AxisValue value;

View File

@@ -16,19 +16,22 @@
class ResourceFilter class ResourceFilter
{ {
public: public:
ResourceFilter() : mData(), mContainsPseudo(false) {} ResourceFilter() : mData(), mContainsPseudoAccented(false),
mContainsPseudoBidi(false) {}
status_t parse(const char* arg); status_t parse(const char* arg);
bool isEmpty() const; bool isEmpty() const;
bool match(int axis, const ResTable_config& config) const; bool match(int axis, const ResTable_config& config) const;
bool match(const ResTable_config& config) const; bool match(const ResTable_config& config) const;
const SortedVector<AxisValue>* configsForAxis(int axis) const; const SortedVector<AxisValue>* configsForAxis(int axis) const;
inline bool containsPseudo() const { return mContainsPseudo; } inline bool containsPseudo() const { return mContainsPseudoAccented; }
inline bool containsPseudoBidi() const { return mContainsPseudoBidi; }
private: private:
bool match(int axis, const AxisValue& value) const; bool match(int axis, const AxisValue& value) const;
KeyedVector<int,SortedVector<AxisValue> > mData; KeyedVector<int,SortedVector<AxisValue> > mData;
bool mContainsPseudo; bool mContainsPseudoAccented;
bool mContainsPseudoBidi;
}; };
#endif #endif

View File

@@ -577,7 +577,7 @@ status_t parseAndAddBag(Bundle* bundle,
int32_t curFormat, int32_t curFormat,
bool isFormatted, bool isFormatted,
const String16& product, const String16& product,
bool pseudolocalize, PseudolocalizationMethod pseudolocalize,
const bool overwrite, const bool overwrite,
ResourceTable* outTable) ResourceTable* outTable)
{ {
@@ -672,7 +672,7 @@ status_t parseAndAddEntry(Bundle* bundle,
int32_t curFormat, int32_t curFormat,
bool isFormatted, bool isFormatted,
const String16& product, const String16& product,
bool pseudolocalize, PseudolocalizationMethod pseudolocalize,
const bool overwrite, const bool overwrite,
KeyedVector<type_ident_pair_t, bool>* skippedResourceNames, KeyedVector<type_ident_pair_t, bool>* skippedResourceNames,
ResourceTable* outTable) ResourceTable* outTable)
@@ -854,10 +854,16 @@ status_t compileResourceFile(Bundle* bundle,
ResTable_config curParams(defParams); ResTable_config curParams(defParams);
ResTable_config pseudoParams(curParams); ResTable_config pseudoParams(curParams);
pseudoParams.language[0] = 'z'; pseudoParams.language[0] = 'e';
pseudoParams.language[1] = 'z'; pseudoParams.language[1] = 'n';
pseudoParams.country[0] = 'Z'; pseudoParams.country[0] = 'X';
pseudoParams.country[1] = 'Z'; pseudoParams.country[1] = 'A';
ResTable_config pseudoBidiParams(curParams);
pseudoBidiParams.language[0] = 'a';
pseudoBidiParams.language[1] = 'r';
pseudoBidiParams.country[0] = 'X';
pseudoBidiParams.country[1] = 'B';
while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::START_TAG) { if (code == ResXMLTree::START_TAG) {
@@ -1334,6 +1340,7 @@ status_t compileResourceFile(Bundle* bundle,
name, name,
locale, locale,
SourcePos(in->getPrintableSource(), block.getLineNumber())); SourcePos(in->getPrintableSource(), block.getLineNumber()));
curIsPseudolocalizable = fileIsTranslatable;
} }
if (formatted == false16) { if (formatted == false16) {
@@ -1389,6 +1396,7 @@ status_t compileResourceFile(Bundle* bundle,
curTag = &plurals16; curTag = &plurals16;
curType = plurals16; curType = plurals16;
curIsBag = true; curIsBag = true;
curIsPseudolocalizable = fileIsTranslatable;
} else if (strcmp16(block.getElementName(&len), array16.string()) == 0) { } else if (strcmp16(block.getElementName(&len), array16.string()) == 0) {
curTag = &array16; curTag = &array16;
curType = array16; curType = array16;
@@ -1410,26 +1418,24 @@ status_t compileResourceFile(Bundle* bundle,
} else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) { } else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) {
// Check whether these strings need valid formats. // Check whether these strings need valid formats.
// (simplified form of what string16 does above) // (simplified form of what string16 does above)
bool isTranslatable = false;
size_t n = block.getAttributeCount(); size_t n = block.getAttributeCount();
// Pseudolocalizable by default, unless this string array isn't // Pseudolocalizable by default, unless this string array isn't
// translatable. // translatable.
curIsPseudolocalizable = true;
for (size_t i = 0; i < n; i++) { for (size_t i = 0; i < n; i++) {
size_t length; size_t length;
const uint16_t* attr = block.getAttributeName(i, &length); const uint16_t* attr = block.getAttributeName(i, &length);
if (strcmp16(attr, translatable16.string()) == 0) {
const uint16_t* value = block.getAttributeStringValue(i, &length);
if (strcmp16(value, false16.string()) == 0) {
curIsPseudolocalizable = false;
}
}
if (strcmp16(attr, formatted16.string()) == 0) { if (strcmp16(attr, formatted16.string()) == 0) {
const uint16_t* value = block.getAttributeStringValue(i, &length); const uint16_t* value = block.getAttributeStringValue(i, &length);
if (strcmp16(value, false16.string()) == 0) { if (strcmp16(value, false16.string()) == 0) {
curIsFormatted = false; curIsFormatted = false;
} }
} else if (strcmp16(attr, translatable16.string()) == 0) {
const uint16_t* value = block.getAttributeStringValue(i, &length);
if (strcmp16(value, false16.string()) == 0) {
isTranslatable = false;
}
} }
} }
@@ -1438,6 +1444,7 @@ status_t compileResourceFile(Bundle* bundle,
curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING; curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
curIsBag = true; curIsBag = true;
curIsBagReplaceOnOverwrite = true; curIsBagReplaceOnOverwrite = true;
curIsPseudolocalizable = isTranslatable && fileIsTranslatable;
} else if (strcmp16(block.getElementName(&len), integer_array16.string()) == 0) { } else if (strcmp16(block.getElementName(&len), integer_array16.string()) == 0) {
curTag = &integer_array16; curTag = &integer_array16;
curType = array16; curType = array16;
@@ -1559,17 +1566,27 @@ status_t compileResourceFile(Bundle* bundle,
err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType, err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType,
ident, parentIdent, itemIdent, curFormat, curIsFormatted, ident, parentIdent, itemIdent, curFormat, curIsFormatted,
product, false, overwrite, outTable); product, NO_PSEUDOLOCALIZATION, overwrite, outTable);
if (err == NO_ERROR) { if (err == NO_ERROR) {
if (curIsPseudolocalizable && localeIsDefined(curParams) if (curIsPseudolocalizable && localeIsDefined(curParams)
&& bundle->getPseudolocalize()) { && bundle->getPseudolocalize() > 0) {
// pseudolocalize here // pseudolocalize here
#if 1 if ((PSEUDO_ACCENTED & bundle->getPseudolocalize()) ==
block.setPosition(parserPosition); PSEUDO_ACCENTED) {
err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage, block.setPosition(parserPosition);
curType, ident, parentIdent, itemIdent, curFormat, err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage,
curIsFormatted, product, true, overwrite, outTable); curType, ident, parentIdent, itemIdent, curFormat,
#endif curIsFormatted, product, PSEUDO_ACCENTED,
overwrite, outTable);
}
if ((PSEUDO_BIDI & bundle->getPseudolocalize()) ==
PSEUDO_BIDI) {
block.setPosition(parserPosition);
err = parseAndAddBag(bundle, in, &block, pseudoBidiParams, myPackage,
curType, ident, parentIdent, itemIdent, curFormat,
curIsFormatted, product, PSEUDO_BIDI,
overwrite, outTable);
}
} }
} }
if (err != NO_ERROR) { if (err != NO_ERROR) {
@@ -1592,20 +1609,31 @@ status_t compileResourceFile(Bundle* bundle,
err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident, err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident,
*curTag, curIsStyled, curFormat, curIsFormatted, *curTag, curIsStyled, curFormat, curIsFormatted,
product, false, overwrite, &skippedResourceNames, outTable); product, NO_PSEUDOLOCALIZATION, overwrite, &skippedResourceNames, outTable);
if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR? if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR?
hasErrors = localHasErrors = true; hasErrors = localHasErrors = true;
} }
else if (err == NO_ERROR) { else if (err == NO_ERROR) {
if (curIsPseudolocalizable && localeIsDefined(curParams) if (curIsPseudolocalizable && localeIsDefined(curParams)
&& bundle->getPseudolocalize()) { && bundle->getPseudolocalize() > 0) {
// pseudolocalize here // pseudolocalize here
block.setPosition(parserPosition); if ((PSEUDO_ACCENTED & bundle->getPseudolocalize()) ==
err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType, PSEUDO_ACCENTED) {
ident, *curTag, curIsStyled, curFormat, block.setPosition(parserPosition);
curIsFormatted, product, err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType,
true, overwrite, &skippedResourceNames, outTable); ident, *curTag, curIsStyled, curFormat,
curIsFormatted, product,
PSEUDO_ACCENTED, overwrite, &skippedResourceNames, outTable);
}
if ((PSEUDO_BIDI & bundle->getPseudolocalize()) ==
PSEUDO_BIDI) {
block.setPosition(parserPosition);
err = parseAndAddEntry(bundle, in, &block, pseudoBidiParams,
myPackage, curType, ident, *curTag, curIsStyled, curFormat,
curIsFormatted, product,
PSEUDO_BIDI, overwrite, &skippedResourceNames, outTable);
}
if (err != NO_ERROR) { if (err != NO_ERROR) {
hasErrors = localHasErrors = true; hasErrors = localHasErrors = true;
} }
@@ -2636,8 +2664,8 @@ ResourceTable::validateLocalizations(void)
continue; continue;
} }
// don't bother with the pseudolocale "zz_ZZ" // don't bother with the pseudolocale "en_XA" or "ar_XB"
if (config != "zz_ZZ") { if (config != "en_XA" && config != "ar_XB") {
if (configSrcMap.find(config) == configSrcMap.end()) { if (configSrcMap.find(config) == configSrcMap.end()) {
// okay, no specific localization found. it's possible that we are // okay, no specific localization found. it's possible that we are
// requiring a specific regional localization [e.g. de_DE] but there is an // requiring a specific regional localization [e.g. de_DE] but there is an

View File

@@ -187,7 +187,7 @@ status_t parseStyledString(Bundle* bundle,
String16* outString, String16* outString,
Vector<StringPool::entry_style_span>* outSpans, Vector<StringPool::entry_style_span>* outSpans,
bool isFormatted, bool isFormatted,
bool pseudolocalize) PseudolocalizationMethod pseudolocalize)
{ {
Vector<StringPool::entry_style_span> spanStack; Vector<StringPool::entry_style_span> spanStack;
String16 curString; String16 curString;
@@ -198,21 +198,30 @@ status_t parseStyledString(Bundle* bundle,
size_t len; size_t len;
ResXMLTree::event_code_t code; ResXMLTree::event_code_t code;
// Bracketing if pseudolocalization accented method specified.
if (pseudolocalize == PSEUDO_ACCENTED) {
curString.append(String16(String8("[")));
}
while ((code=inXml->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { while ((code=inXml->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::TEXT) { if (code == ResXMLTree::TEXT) {
String16 text(inXml->getText(&len)); String16 text(inXml->getText(&len));
if (firstTime && text.size() > 0) { if (firstTime && text.size() > 0) {
firstTime = false; firstTime = false;
if (text.string()[0] == '@') { if (text.string()[0] == '@') {
// If this is a resource reference, don't do the pseudoloc. // If this is a resource reference, don't do the pseudoloc.
pseudolocalize = false; pseudolocalize = NO_PSEUDOLOCALIZATION;
} }
} }
if (xliffDepth == 0 && pseudolocalize) { if (xliffDepth == 0 && pseudolocalize > 0) {
std::string orig(String8(text).string()); String16 pseudo;
std::string pseudo = pseudolocalize_string(orig); if (pseudolocalize == PSEUDO_ACCENTED) {
curString.append(String16(String8(pseudo.c_str()))); pseudo = pseudolocalize_string(text);
} else if (pseudolocalize == PSEUDO_BIDI) {
pseudo = pseudobidi_string(text);
} else {
pseudo = text;
}
curString.append(pseudo);
} else { } else {
if (isFormatted && hasSubstitutionErrors(fileName, inXml, text) != NO_ERROR) { if (isFormatted && hasSubstitutionErrors(fileName, inXml, text) != NO_ERROR) {
return UNKNOWN_ERROR; return UNKNOWN_ERROR;
@@ -352,6 +361,25 @@ moveon:
} }
} }
// Bracketing if pseudolocalization accented method specified.
if (pseudolocalize == PSEUDO_ACCENTED) {
const char16_t* str = outString->string();
const char16_t* p = str;
const char16_t* e = p + outString->size();
int words_cnt = 0;
while (p < e) {
if (isspace(*p)) {
words_cnt++;
}
p++;
}
unsigned int length = words_cnt > 3 ? outString->size() :
outString->size() / 2;
curString.append(String16(String8(" ")));
curString.append(pseudo_generate_expansion(length));
curString.append(String16(String8("]")));
}
if (code == ResXMLTree::BAD_DOCUMENT) { if (code == ResXMLTree::BAD_DOCUMENT) {
SourcePos(String8(fileName), inXml->getLineNumber()).error( SourcePos(String8(fileName), inXml->getLineNumber()).error(
"Error parsing XML\n"); "Error parsing XML\n");

View File

@@ -26,7 +26,7 @@ status_t parseStyledString(Bundle* bundle,
String16* outString, String16* outString,
Vector<StringPool::entry_style_span>* outSpans, Vector<StringPool::entry_style_span>* outSpans,
bool isFormatted, bool isFormatted,
bool isPseudolocalizable); PseudolocalizationMethod isPseudolocalizable);
void printXMLBlock(ResXMLTree* block); void printXMLBlock(ResXMLTree* block);

View File

@@ -2,89 +2,155 @@
using namespace std; using namespace std;
// String basis to generate expansion
static const String16 k_expansion_string = String16("one two three "
"four five six seven eight nine ten eleven twelve thirteen "
"fourteen fiveteen sixteen seventeen nineteen twenty");
// Special unicode characters to override directionality of the words
static const String16 k_rlm = String16("\xe2\x80\x8f");
static const String16 k_rlo = String16("\xE2\x80\xae");
static const String16 k_pdf = String16("\xE2\x80\xac");
// Placeholder marks
static const String16 k_placeholder_open = String16("\xc2\xbb");
static const String16 k_placeholder_close = String16("\xc2\xab");
static const char* static const char*
pseudolocalize_char(char c) pseudolocalize_char(const char16_t c)
{ {
switch (c) { switch (c) {
case 'a': return "\xc4\x83"; case 'a': return "\xc3\xa5";
case 'b': return "\xcf\x84"; case 'b': return "\xc9\x93";
case 'c': return "\xc4\x8b"; case 'c': return "\xc3\xa7";
case 'd': return "\xc4\x8f"; case 'd': return "\xc3\xb0";
case 'e': return "\xc4\x99"; case 'e': return "\xc3\xa9";
case 'f': return "\xc6\x92"; case 'f': return "\xc6\x92";
case 'g': return "\xc4\x9d"; case 'g': return "\xc4\x9d";
case 'h': return "\xd1\x9b"; case 'h': return "\xc4\xa5";
case 'i': return "\xcf\x8a"; case 'i': return "\xc3\xae";
case 'j': return "\xc4\xb5"; case 'j': return "\xc4\xb5";
case 'k': return "\xc4\xb8"; case 'k': return "\xc4\xb7";
case 'l': return "\xc4\xba"; case 'l': return "\xc4\xbc";
case 'm': return "\xe1\xb8\xbf"; case 'm': return "\xe1\xb8\xbf";
case 'n': return "\xd0\xb8"; case 'n': return "\xc3\xb1";
case 'o': return "\xcf\x8c"; case 'o': return "\xc3\xb6";
case 'p': return "\xcf\x81"; case 'p': return "\xc3\xbe";
case 'q': return "\x51"; case 'q': return "\x51";
case 'r': return "\xd2\x91"; case 'r': return "\xc5\x95";
case 's': return "\xc5\xa1"; case 's': return "\xc5\xa1";
case 't': return "\xd1\x82"; case 't': return "\xc5\xa3";
case 'u': return "\xce\xb0"; case 'u': return "\xc3\xbb";
case 'v': return "\x56"; case 'v': return "\x56";
case 'w': return "\xe1\xba\x85"; case 'w': return "\xc5\xb5";
case 'x': return "\xd1\x85"; case 'x': return "\xd1\x85";
case 'y': return "\xe1\xbb\xb3"; case 'y': return "\xc3\xbd";
case 'z': return "\xc5\xba"; case 'z': return "\xc5\xbe";
case 'A': return "\xc3\x85"; case 'A': return "\xc3\x85";
case 'B': return "\xce\xb2"; case 'B': return "\xce\xb2";
case 'C': return "\xc4\x88"; case 'C': return "\xc3\x87";
case 'D': return "\xc4\x90"; case 'D': return "\xc3\x90";
case 'E': return "\xd0\x84"; case 'E': return "\xc3\x89";
case 'F': return "\xce\x93"; case 'G': return "\xc4\x9c";
case 'G': return "\xc4\x9e"; case 'H': return "\xc4\xa4";
case 'H': return "\xc4\xa6"; case 'I': return "\xc3\x8e";
case 'I': return "\xd0\x87"; case 'J': return "\xc4\xb4";
case 'J': return "\xc4\xb5";
case 'K': return "\xc4\xb6"; case 'K': return "\xc4\xb6";
case 'L': return "\xc5\x81"; case 'L': return "\xc4\xbb";
case 'M': return "\xe1\xb8\xbe"; case 'M': return "\xe1\xb8\xbe";
case 'N': return "\xc5\x83"; case 'N': return "\xc3\x91";
case 'O': return "\xce\x98"; case 'O': return "\xc3\x96";
case 'P': return "\xcf\x81"; case 'P': return "\xc3\x9e";
case 'Q': return "\x71"; case 'Q': return "\x71";
case 'R': return "\xd0\xaf"; case 'R': return "\xc5\x94";
case 'S': return "\xc8\x98"; case 'S': return "\xc5\xa0";
case 'T': return "\xc5\xa6"; case 'T': return "\xc5\xa2";
case 'U': return "\xc5\xa8"; case 'U': return "\xc3\x9b";
case 'V': return "\xce\xbd"; case 'V': return "\xce\xbd";
case 'W': return "\xe1\xba\x84"; case 'W': return "\xc5\xb4";
case 'X': return "\xc3\x97"; case 'X': return "\xc3\x97";
case 'Y': return "\xc2\xa5"; case 'Y': return "\xc3\x9d";
case 'Z': return "\xc5\xbd"; case 'Z': return "\xc5\xbd";
case '!': return "\xc2\xa1";
case '?': return "\xc2\xbf";
case '$': return "\xe2\x82\xac";
default: return NULL; default: return NULL;
} }
} }
static const bool
is_possible_normal_placeholder_end(const char16_t c) {
switch (c) {
case 's': return true;
case 'S': return true;
case 'c': return true;
case 'C': return true;
case 'd': return true;
case 'o': return true;
case 'x': return true;
case 'X': return true;
case 'f': return true;
case 'e': return true;
case 'E': return true;
case 'g': return true;
case 'G': return true;
case 'a': return true;
case 'A': return true;
case 'b': return true;
case 'B': return true;
case 'h': return true;
case 'H': return true;
case '%': return true;
case 'n': return true;
default: return false;
}
}
String16
pseudo_generate_expansion(const unsigned int length) {
String16 result = k_expansion_string;
const char16_t* s = result.string();
if (result.size() < length) {
result += String16(" ");
result += pseudo_generate_expansion(length - result.size());
} else {
int ext = 0;
// Should contain only whole words, so looking for a space
for (unsigned int i = length + 1; i < result.size(); ++i) {
++ext;
if (s[i] == ' ') {
break;
}
}
result.remove(length + ext, 0);
}
return result;
}
/** /**
* Converts characters so they look like they've been localized. * Converts characters so they look like they've been localized.
* *
* Note: This leaves escape sequences untouched so they can later be * Note: This leaves escape sequences untouched so they can later be
* processed by ResTable::collectString in the normal way. * processed by ResTable::collectString in the normal way.
*/ */
string String16
pseudolocalize_string(const string& source) pseudolocalize_string(const String16& source)
{ {
const char* s = source.c_str(); const char16_t* s = source.string();
string result; String16 result;
const size_t I = source.length(); const size_t I = source.size();
for (size_t i=0; i<I; i++) { for (size_t i=0; i<I; i++) {
char c = s[i]; char16_t c = s[i];
if (c == '\\') { if (c == '\\') {
// Escape syntax, no need to pseudolocalize
if (i<I-1) { if (i<I-1) {
result += '\\'; result += String16("\\");
i++; i++;
c = s[i]; c = s[i];
switch (c) { switch (c) {
case 'u': case 'u':
// this one takes up 5 chars // this one takes up 5 chars
result += string(s+i, 5); result += String16(s+i, 5);
i += 4; i += 4;
break; break;
case 't': case 't':
@@ -96,24 +162,107 @@ pseudolocalize_string(const string& source)
case '\'': case '\'':
case '\\': case '\\':
default: default:
result += c; result.append(&c, 1);
break; break;
} }
} else { } else {
result += c; result.append(&c, 1);
}
} else if (c == '%') {
// Placeholder syntax, no need to pseudolocalize
result += k_placeholder_open;
bool end = false;
result.append(&c, 1);
while (!end && i < I) {
++i;
c = s[i];
result.append(&c, 1);
if (is_possible_normal_placeholder_end(c)) {
end = true;
} else if (c == 't') {
++i;
c = s[i];
result.append(&c, 1);
end = true;
}
}
result += k_placeholder_close;
} else if (c == '<' || c == '&') {
// html syntax, no need to pseudolocalize
bool tag_closed = false;
while (!tag_closed && i < I) {
if (c == '&') {
String16 escape_text;
escape_text.append(&c, 1);
bool end = false;
size_t htmlCodePos = i;
while (!end && htmlCodePos < I) {
++htmlCodePos;
c = s[htmlCodePos];
escape_text.append(&c, 1);
// Valid html code
if (c == ';') {
end = true;
i = htmlCodePos;
}
// Wrong html code
else if (!((c == '#' ||
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9')))) {
end = true;
}
}
result += escape_text;
if (escape_text != String16("&lt;")) {
tag_closed = true;
}
continue;
}
if (c == '>') {
tag_closed = true;
result.append(&c, 1);
continue;
}
result.append(&c, 1);
i++;
c = s[i];
} }
} else { } else {
// This is a pure text that should be pseudolocalized
const char* p = pseudolocalize_char(c); const char* p = pseudolocalize_char(c);
if (p != NULL) { if (p != NULL) {
result += p; result += String16(p);
} else { } else {
result += c; result.append(&c, 1);
} }
} }
} }
//printf("result=\'%s\'\n", result.c_str());
return result; return result;
} }
String16
pseudobidi_string(const String16& source)
{
const char16_t* s = source.string();
String16 result;
result += k_rlm;
result += k_rlo;
for (size_t i=0; i<source.size(); i++) {
char16_t c = s[i];
switch(c) {
case ' ': result += k_pdf;
result += k_rlm;
result.append(&c, 1);
result += k_rlm;
result += k_rlo;
break;
default: result.append(&c, 1);
break;
}
}
result += k_pdf;
result += k_rlm;
return result;
}

View File

@@ -1,9 +1,18 @@
#ifndef HOST_PSEUDOLOCALIZE_H #ifndef HOST_PSEUDOLOCALIZE_H
#define HOST_PSEUDOLOCALIZE_H #define HOST_PSEUDOLOCALIZE_H
#include "StringPool.h"
#include <string> #include <string>
std::string pseudolocalize_string(const std::string& source); String16 pseudolocalize_string(const String16& source);
// Surrounds every word in the sentance with specific characters that makes
// the word directionality RTL.
String16 pseudobidi_string(const String16& source);
// Generates expansion string based on the specified lenght.
// Generated string could not be shorter that length, but it could be slightly
// longer.
String16 pseudo_generate_expansion(const unsigned int length);
#endif // HOST_PSEUDOLOCALIZE_H #endif // HOST_PSEUDOLOCALIZE_H