546 lines
20 KiB
C++
546 lines
20 KiB
C++
//
|
|
// Created by kaishiqi on 14-8-5.
|
|
// Copyright (c) 2014 kaishiqi. All rights reserved.
|
|
//
|
|
|
|
#include "I18nUtils.h"
|
|
#include <cstdlib>
|
|
#include <cstdarg>
|
|
|
|
#ifdef __GNUC__
|
|
#include <regex.h>
|
|
#else
|
|
#include <regex>
|
|
#endif
|
|
|
|
NS_I18N_BEGIN
|
|
|
|
namespace {
|
|
// The max length of formatMatch message.
|
|
static const int MAX_LOG_LENGTH = 100*1024;
|
|
|
|
/** The singleton pointer of I18nUtils. */
|
|
static I18nUtils* _instance = nullptr;
|
|
}
|
|
|
|
string I18nUtils::DEFAULT_DOMAIN = "default";
|
|
|
|
I18nUtils* I18nUtils::getInstance()
|
|
{
|
|
if (!_instance)
|
|
_instance = new I18nUtils();
|
|
return _instance;
|
|
}
|
|
|
|
void I18nUtils::destroyInstance()
|
|
{
|
|
if (_instance) {
|
|
delete _instance;
|
|
_instance = nullptr;
|
|
}
|
|
}
|
|
|
|
I18nUtils::I18nUtils()
|
|
{
|
|
}
|
|
|
|
I18nUtils::~I18nUtils()
|
|
{
|
|
}
|
|
|
|
void I18nUtils::addMO(MO mo, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
domain = (!domain.empty() ? domain : DEFAULT_DOMAIN);
|
|
_moMap[domain] = mo;
|
|
}
|
|
|
|
void I18nUtils::addMO(string filePath, std::function<int(int n)> onPluralExpression/*=MO::DEFAULT_PLURAL_EXPRESSION*/, unsigned int nplurals/*=MO::DEFAULT_NPLURALS*/, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
if (!filePath.empty()) {
|
|
domain = (!domain.empty() ? domain : DEFAULT_DOMAIN);
|
|
_moMap[domain] = MO(filePath, onPluralExpression, nplurals);
|
|
}
|
|
}
|
|
|
|
void I18nUtils::addMO(unsigned char* bytes, std::function<int(int n)> onPluralExpression/*=MO::DEFAULT_PLURAL_EXPRESSION*/, unsigned int nplurals/*=MO::DEFAULT_NPLURALS*/, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
if (bytes) {
|
|
domain = (!domain.empty() ? domain : DEFAULT_DOMAIN);
|
|
_moMap[domain] = MO(bytes, onPluralExpression, nplurals);
|
|
}
|
|
}
|
|
|
|
void I18nUtils::removeMO(string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
auto moItr = _moMap.find(domain);
|
|
if (moItr != _moMap.end()) {
|
|
_moMap.erase(moItr);
|
|
}
|
|
}
|
|
|
|
void I18nUtils::removeAllMO()
|
|
{
|
|
_moMap.clear();
|
|
}
|
|
|
|
bool I18nUtils::hasMO(string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
auto moItr = _moMap.find(domain);
|
|
return (moItr != _moMap.end());
|
|
}
|
|
|
|
const MO& I18nUtils::getMO(string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
if (domain.empty()) {
|
|
domain = DEFAULT_DOMAIN;
|
|
}
|
|
|
|
if (!hasMO(domain)) {
|
|
_moMap[domain] = MO();
|
|
}
|
|
|
|
return _moMap[domain];
|
|
}
|
|
|
|
string I18nUtils::translate(string singular, string context/*=""*/, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
auto moItr = _moMap.find(domain);
|
|
auto hasMo = (moItr != _moMap.end());
|
|
return (hasMo ? (*moItr).second.translate(singular, context) : singular);
|
|
}
|
|
|
|
string I18nUtils::translatePlural(string singular, string plural, int count, string context/*=""*/, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
auto moItr = _moMap.find(domain);
|
|
auto hasMo = (moItr != _moMap.end());
|
|
|
|
if (hasMo) {
|
|
return (*moItr).second.translatePlural(singular, plural, count, context);
|
|
} else {
|
|
return (1==count) ? singular : plural;
|
|
}
|
|
}
|
|
|
|
string I18nUtils::gettext(string text, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
return translate(text, "", domain);
|
|
}
|
|
|
|
string I18nUtils::xgettext(string text, string context, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
return translate(text, context, domain);
|
|
}
|
|
|
|
string I18nUtils::ngettext(string singular, string plural, int number, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
return translatePlural(singular, plural, number, "", domain);
|
|
}
|
|
|
|
string I18nUtils::nxgettext(string singular, string plural, int number, string context, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
return translatePlural(singular, plural, number, context, domain);
|
|
}
|
|
|
|
NoopEntry I18nUtils::ngettextNoop(string singular, string plural, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
NoopEntry entry;
|
|
entry.setIsPural(true);
|
|
entry.setSingular(singular);
|
|
entry.setPlural(plural);
|
|
entry.setDomain(domain);
|
|
return entry;
|
|
}
|
|
|
|
NoopEntry I18nUtils::nxgettextNoop(string singular, string plural, string context, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
NoopEntry entry;
|
|
entry.setIsPural(true);
|
|
entry.setSingular(singular);
|
|
entry.setPlural(plural);
|
|
entry.setContext(context);
|
|
entry.setDomain(domain);
|
|
return entry;
|
|
}
|
|
|
|
string I18nUtils::translateNooped(NoopEntry& entry, int number, string domain/*=DEFAULT_DOMAIN*/)
|
|
{
|
|
auto domainString = entry.getDomain().empty() ? domain : entry.getDomain();
|
|
return translatePlural(entry.getSingular(), entry.getPlural(), number, entry.getContext(), domainString);
|
|
}
|
|
|
|
#if (IS_ENABLE_FORMAT_MATCH_ARGS_INDEX == FORMAT_MATCH_ENABLE)
|
|
#ifdef __GNUC__
|
|
std::string I18nUtils::formatMatch(const char* format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
string result(format);
|
|
|
|
/** precache some functions */
|
|
auto getRegMatchStr = [](regmatch_t match, const char* target) {
|
|
auto len = match.rm_eo - match.rm_so;
|
|
char* chars = new char[len+1];// +1 is null-terminated byte string pointed
|
|
strncpy(chars, &target[match.rm_so], len);
|
|
chars[len] = 0;
|
|
std::string str(chars);
|
|
return str;
|
|
};
|
|
|
|
//printf style format regex: %[align][flags][width][.prec][type]specifier
|
|
static const string PRINTF_FORMAT_PATTERN = "%(\\-*)([\\+\\# 0]*)([0-9]*)(\\.?[0-9]*)([jztL]?|[hl]{0,2})([csdioxXufFeEaAgGp])";
|
|
static const size_t PRINTF_FORMAT_MACTH_NUM = 6+1;//sub match num + 1(total self)
|
|
|
|
static const string FORMAT_MATCH_PATTERN = PRINTF_FORMAT_PATTERN + "([0-9]+)";
|
|
static const size_t FORMAT_MATCH_MATCH_NUM = PRINTF_FORMAT_MACTH_NUM + 1;
|
|
|
|
struct FormatMatchResultInfo {
|
|
string format; // printf style format string
|
|
string type; // printf style format type
|
|
char specifier; // printf style format specifier
|
|
string numStr; // formatMatch format args number string
|
|
int index; // formatMatch format args number
|
|
};
|
|
|
|
|
|
/** match all conversion specifier */
|
|
regmatch_t formatMatchMatches[FORMAT_MATCH_MATCH_NUM];
|
|
regex_t formatMatchRegex;
|
|
regcomp(&formatMatchRegex, FORMAT_MATCH_PATTERN.c_str(), REG_EXTENDED);
|
|
|
|
//[FormatMatchResultInfo.index] = FormatMatchResultInfo
|
|
std::unordered_map<int, FormatMatchResultInfo> matchItrsMap;
|
|
|
|
string formatClone(format);
|
|
while (true) {
|
|
auto formatMatchStatic = regexec(&formatMatchRegex, formatClone.c_str(), FORMAT_MATCH_MATCH_NUM, formatMatchMatches, 0);
|
|
if (0 == formatMatchStatic) {//static 0 = find
|
|
auto formatMatch = formatMatchMatches[0];
|
|
auto argNumMatch = formatMatchMatches[FORMAT_MATCH_MATCH_NUM-1];
|
|
auto specifierMatch = formatMatchMatches[FORMAT_MATCH_MATCH_NUM-2];
|
|
auto typeMatch = formatMatchMatches[FORMAT_MATCH_MATCH_NUM-3];
|
|
|
|
FormatMatchResultInfo matchInfo;
|
|
matchInfo.numStr = getRegMatchStr(argNumMatch, formatClone.c_str());
|
|
matchInfo.specifier = getRegMatchStr(specifierMatch, formatClone.c_str())[0];
|
|
matchInfo.type = getRegMatchStr(typeMatch, formatClone.c_str());
|
|
matchInfo.index = std::atoi(matchInfo.numStr.c_str()) - 1;
|
|
|
|
auto matchFormat = getRegMatchStr(formatMatch, formatClone.c_str());
|
|
matchInfo.format = matchFormat.substr(0, matchFormat.size() - matchInfo.numStr.size());
|
|
|
|
if (0 <= matchInfo.index) {
|
|
auto numItr = matchItrsMap.find(matchInfo.index);
|
|
|
|
if (numItr == matchItrsMap.end()) {
|
|
matchItrsMap[matchInfo.index] = matchInfo;
|
|
}
|
|
}
|
|
|
|
for (auto i = formatMatch.rm_so; i < formatMatch.rm_eo; i++) {
|
|
formatClone[i] = '_';
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
regfree(&formatMatchRegex);
|
|
|
|
|
|
/** replace the all conversion format with corresponding index argument */
|
|
for (int i = 0; i < matchItrsMap.size(); i++) {
|
|
auto matchItr = matchItrsMap.find(i);
|
|
if (matchItr != matchItrsMap.end()) {
|
|
auto matchInfo = matchItrsMap[i];
|
|
char buf[MAX_LOG_LENGTH];
|
|
|
|
// get a format argument
|
|
switch (matchInfo.specifier) {
|
|
case 'c':
|
|
if ("l" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (wchar_t)va_arg(args, int));
|
|
} else {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (char)va_arg(args, int));
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
if ("l" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, wchar_t*));
|
|
} else {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, char*));
|
|
}
|
|
break;
|
|
|
|
case 'd':
|
|
case 'i':
|
|
case 'o':
|
|
case 'x':
|
|
case 'X':
|
|
case 'u':
|
|
if ("hh" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (unsigned char)va_arg(args, int));
|
|
} else if ("h" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (unsigned short)va_arg(args, int));
|
|
} else if ("l" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (unsigned long)va_arg(args, long));
|
|
} else if ("ll" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (unsigned long long)va_arg(args, long long));
|
|
} else {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (unsigned int)va_arg(args, int));
|
|
}
|
|
break;
|
|
|
|
case 'f':
|
|
case 'F':
|
|
if ("L" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, long double));
|
|
} else {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, double));
|
|
}
|
|
break;
|
|
|
|
case 'e':
|
|
case 'E':
|
|
case 'a':
|
|
case 'A':
|
|
case 'g':
|
|
case 'G':
|
|
if ("L" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, long double));
|
|
} else {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, double));
|
|
}
|
|
break;
|
|
|
|
case 'p':
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, void*));
|
|
break;
|
|
|
|
default:
|
|
//unsupported specifier
|
|
break;
|
|
}
|
|
|
|
// replace the conversion format with argument
|
|
string replacePattern(PRINTF_FORMAT_PATTERN + "(" + matchInfo.numStr + "[0-9]*)");
|
|
regmatch_t replaceMatchMatches[FORMAT_MATCH_MATCH_NUM];
|
|
regex_t replaceMatchRegex;
|
|
regcomp(&replaceMatchRegex, replacePattern.c_str(), REG_EXTENDED);
|
|
|
|
string resultClone = result;
|
|
bool hasReplace = false;
|
|
do {
|
|
hasReplace = false;
|
|
|
|
while (true) {
|
|
auto replaceMatchStatic = regexec(&replaceMatchRegex, resultClone.c_str(), FORMAT_MATCH_MATCH_NUM, replaceMatchMatches, 0);
|
|
if (0 == replaceMatchStatic) {//static 0 = find
|
|
auto replaceMatch = replaceMatchMatches[0];
|
|
auto argNumMatch = replaceMatchMatches[FORMAT_MATCH_MATCH_NUM - 1];
|
|
auto argNumStr = getRegMatchStr(argNumMatch, result.c_str());
|
|
|
|
if (argNumStr == matchInfo.numStr) {
|
|
auto resultLen = resultClone.size();
|
|
resultLen -= replaceMatch.rm_eo - replaceMatch.rm_so;
|
|
resultLen += strlen(buf);
|
|
|
|
char* resultCloneStr = new char[resultLen+1];
|
|
strncpy(resultCloneStr, &resultClone[0], replaceMatch.rm_so);
|
|
strcpy(&resultCloneStr[replaceMatch.rm_so], buf);
|
|
strcpy(&resultCloneStr[replaceMatch.rm_so + strlen(buf)], &resultClone[replaceMatch.rm_eo]);
|
|
resultCloneStr[resultLen] = 0;
|
|
resultClone = resultCloneStr;
|
|
delete [] resultCloneStr;
|
|
|
|
char* resultStr = new char[resultLen+1];
|
|
strncpy(resultStr, &result[0], replaceMatch.rm_so);
|
|
strcpy(&resultStr[replaceMatch.rm_so], buf);
|
|
strcpy(&resultStr[replaceMatch.rm_so + strlen(buf)], &result[replaceMatch.rm_eo]);
|
|
resultStr[resultLen] = 0;
|
|
result = resultStr;
|
|
delete [] resultStr;
|
|
|
|
hasReplace = true;
|
|
break;
|
|
}
|
|
|
|
for (auto i = replaceMatch.rm_so; i < replaceMatch.rm_eo; i++) {
|
|
resultClone[i] = '_';
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
} while (hasReplace);
|
|
regfree(&replaceMatchRegex);
|
|
}
|
|
}
|
|
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
#else
|
|
string I18nUtils::formatMatch(const char* format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
string result(format);
|
|
|
|
//printf style format regex: %[align][flags][width][.prec][type]specifier
|
|
static const string PRINTF_FORMAT_PATTERN("%(\\-*)([\\+\\# 0]*)(\\d*)(\\.?\\d*)([jztL]?|[hl]{0,2})([csdioxXufFeEaAgGp])");
|
|
std::regex formatMatchPattern(PRINTF_FORMAT_PATTERN + "(\\d+)");
|
|
|
|
struct FormatMatchResultInfo {
|
|
string format; // printf style format string
|
|
string type; // printf style format type
|
|
char specifier; // printf style format specifier
|
|
string numStr; // formatMatch format args number string
|
|
int index; // formatMatch format args number
|
|
};
|
|
|
|
/** match all conversion specifier */
|
|
auto formatBegin = std::sregex_iterator(result.begin(), result.end(), formatMatchPattern);
|
|
auto formatEnd = std::sregex_iterator();
|
|
|
|
//[FormatMatchResultInfo.index] = FormatMatchResultInfo
|
|
std::unordered_map<int, FormatMatchResultInfo> matchItrsMap;
|
|
for (auto itr = formatBegin; itr != formatEnd; ++itr) {
|
|
auto matchResult = (*itr);
|
|
auto matchFormat = matchResult.str();
|
|
|
|
FormatMatchResultInfo matchInfo;
|
|
matchInfo.numStr = matchResult[matchResult.size() - 1].str();
|
|
matchInfo.specifier = matchResult[matchResult.size() - 2].str()[0];
|
|
matchInfo.type = matchResult[matchResult.size() - 3].str();
|
|
matchInfo.index = std::atoi(matchInfo.numStr.c_str()) - 1;
|
|
matchInfo.format = matchFormat.substr(0, matchFormat.size() - matchInfo.numStr.size());
|
|
|
|
if (0 <= matchInfo.index) {
|
|
auto numItr = matchItrsMap.find(matchInfo.index);
|
|
|
|
if (numItr == matchItrsMap.end()) {
|
|
matchItrsMap[matchInfo.index] = matchInfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** replace the all conversion format with corresponding index argument */
|
|
for (int i = 0; i < matchItrsMap.size(); i++) {
|
|
auto matchItr = matchItrsMap.find(i);
|
|
if (matchItr != matchItrsMap.end()) {
|
|
auto matchInfo = matchItrsMap[i];
|
|
char buf[MAX_LOG_LENGTH];
|
|
|
|
// get a format argument
|
|
switch (matchInfo.specifier) {
|
|
case 'c':
|
|
if ("l" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (wchar_t)va_arg(args, int));
|
|
} else {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (char)va_arg(args, int));
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
if ("l" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, wchar_t*));
|
|
} else {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, char*));
|
|
}
|
|
break;
|
|
|
|
case 'd':
|
|
case 'i':
|
|
case 'o':
|
|
case 'x':
|
|
case 'X':
|
|
case 'u':
|
|
if ("hh" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (unsigned char)va_arg(args, int));
|
|
} else if ("h" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (unsigned short)va_arg(args, int));
|
|
} else if ("l" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (unsigned long)va_arg(args, long));
|
|
} else if ("ll" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (unsigned long long)va_arg(args, long long));
|
|
} else {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), (unsigned int)va_arg(args, int));
|
|
}
|
|
break;
|
|
|
|
case 'f':
|
|
case 'F':
|
|
if ("L" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, long double));
|
|
} else {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, double));
|
|
}
|
|
break;
|
|
|
|
case 'e':
|
|
case 'E':
|
|
case 'a':
|
|
case 'A':
|
|
case 'g':
|
|
case 'G':
|
|
if ("L" == matchInfo.type) {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, long double));
|
|
} else {
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, double));
|
|
}
|
|
break;
|
|
|
|
case 'p':
|
|
snprintf(buf, MAX_LOG_LENGTH-3, matchInfo.format.c_str(), va_arg(args, void*));
|
|
break;
|
|
|
|
default:
|
|
//unsupported specifier
|
|
break;
|
|
}
|
|
|
|
// replace the conversion format with argument
|
|
string subPattern("(" + matchInfo.numStr + "\\d*)");
|
|
std::regex replacePattern(PRINTF_FORMAT_PATTERN + subPattern);
|
|
|
|
bool hasReplace = false;
|
|
do {
|
|
auto words_begin = std::sregex_iterator(result.begin(), result.end(), replacePattern);
|
|
auto words_end = std::sregex_iterator();
|
|
hasReplace = false;
|
|
|
|
for (auto itr = words_begin; itr != words_end; ++itr) {
|
|
auto replaceResult = *itr;
|
|
auto replaceMatch = replaceResult[0];
|
|
auto resultNumStr = replaceResult[replaceResult.size() - 1].str();
|
|
|
|
if (resultNumStr == matchInfo.numStr) {
|
|
result.replace(replaceMatch.first, replaceMatch.second, buf);
|
|
hasReplace = true;
|
|
break;
|
|
}
|
|
}
|
|
} while (hasReplace);
|
|
}
|
|
}
|
|
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
#endif
|
|
#else
|
|
string I18nUtils::formatMatch(const char* format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
char buf[MAX_LOG_LENGTH];
|
|
vsnprintf(buf, MAX_LOG_LENGTH-3, format, args);
|
|
string result(buf);
|
|
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
NS_I18N_END |