/* * An Implementation of i18n * Nana C++ Library(http://www.nanapro.org) * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) * * @file: nana/internationalization.cpp */ #include #include #include #include #include #if defined(STD_THREAD_NOT_SUPPORTED) #include #else #include #endif #include namespace nana { namespace internationalization_parts { enum class token { msgid, msgstr, string, eof }; //Forward declaration void use_eval(); class tokenizer { public: tokenizer(const std::string& file, bool utf8) { std::ifstream ifs(file.data(), std::ios::binary); if (ifs) { ifs.seekg(0, std::ios::end); auto len = static_cast(ifs.tellg()); ifs.seekg(0, std::ios::beg); if (len > 0) { data_.reset(new char[len]); ifs.read(data_.get(), len); read_ptr_ = data_.get(); if (utf8 && len > 3) { if (static_cast(read_ptr_[0]) == 0xEF && static_cast(read_ptr_[1]) == 0xBB && static_cast(read_ptr_[2]) == 0xBF) read_ptr_ += 3; } end_ptr_ = read_ptr_ + len; } } } token read() { if (read_ptr_ == end_ptr_) return token::eof; str_.clear(); _m_eat_ws(); if (*read_ptr_ == '"') { bool reach_right_quota; while (true) { reach_right_quota = false; bool escape = false; for (auto i = read_ptr_ + 1; i != end_ptr_; ++i) { if (escape) { escape = false; str_ += '\\'; str_ += *i; continue; } if ('"' == *i) { read_ptr_ = i + 1; reach_right_quota = true; break; } else if ('\\' != *i) str_ += *i; else escape = true; } _m_eat_ws(); if (read_ptr_ == end_ptr_ || '"' != *read_ptr_) break; } if (reach_right_quota) return token::string; } else if ('a' <= *read_ptr_ && *read_ptr_ <= 'z') { for (auto i = read_ptr_; i != end_ptr_; ++i) { if (*i < 'a' || 'z' < *i) { std::string id(read_ptr_, i); read_ptr_ = i; if (id == "msgid") return token::msgid; else if (id == "msgstr") return token::msgstr; break; } } read_ptr_ = end_ptr_; } return token::eof; } std::string& get_str() { return str_; } private: void _m_eat_ws() { for (auto i = read_ptr_; i != end_ptr_; ++i) { switch (*i) { case ' ': case '\t': case '\r': case '\n': break; default: read_ptr_ = i; return; } } read_ptr_ = end_ptr_; } private: std::unique_ptr data_; const char * read_ptr_{ nullptr }; const char * end_ptr_{ nullptr }; std::string str_; };//end class tokenizer struct data { std::function on_missing; std::unordered_map table; data() { //initializes nana's translation table["NANA_BUTTON_OK"] = "OK"; table["NANA_BUTTON_OK_SHORTKEY"] = "&OK"; table["NANA_BUTTON_YES"] = "Yes"; table["NANA_BUTTON_NO"] = "No"; table["NANA_BUTTON_BROWSE"] = "Browse"; table["NANA_BUTTON_CANCEL"] = "Cancel"; table["NANA_BUTTON_CANCEL_SHORTKEY"] = "&Cancel"; table["NANA_BUTTON_CREATE"] = "Create"; table["NANA_FILEBOX_BYTES"] = "Bytes"; table["NANA_FILEBOX_FILESYSTEM"] = "FILESYSTEM"; table["NANA_FILEBOX_FILTER"] = "Filter"; table["NANA_FILEBOX_NEW_FOLDER"] = "New Folder"; table["NANA_FILEBOX_NEW_FOLDER_SHORTKEY"] = "&New Folder"; table["NANA_FILEBOX_HEADER_NAME"] = "Name"; table["NANA_FILEBOX_HEADER_MODIFIED"] = "Modified"; table["NANA_FILEBOX_HEADER_TYPE"] = "Type"; table["NANA_FILEBOX_HEADER_SIZE"] = "Size"; table["NANA_FILEBOX_NEW_FOLDER_CAPTION"] = "Name the new folder"; table["NANA_FILEBOX_SAVE_AS"] = "Save As"; table["NANA_FILEBOX_OPEN"] = "Open"; table["NANA_FILEBOX_OPEN_DIRECTORY"] = "Select A Directory"; table["NANA_FILEBOX_FILE"] = "File"; table["NANA_FILEBOX_FILE_COLON"] = "File:"; table["NANA_FILEBOX_DIRECTORY"] = "Directory"; table["NANA_FILEBOX_DIRECTORY_COLON"] = "Directory:"; table["NANA_FILEBOX_ERROR_INVALID_FOLDER_NAME"] = "Please input a valid name for the new folder."; table["NANA_FILEBOX_ERROR_RENAME_FOLDER_BECAUSE_OF_EXISTING"] = "The folder is existing, please rename it."; table["NANA_FILEBOX_ERROR_RENAME_FOLDER_BECAUSE_OF_FAILED_CREATION"] = "Failed to create the folder, please rename it."; table["NANA_FILEBOX_ERROR_INVALID_FILENAME"] = "\"%arg0\"\nThe filename is invalid."; table["NANA_FILEBOX_ERROR_NOT_EXISTING_AND_RETRY"] = "The file \"%arg0\"\n is not existing. Please check and retry."; table["NANA_FILEBOX_ERROR_DIRECTORY_NOT_EXISTING_AND_RETRY"] = "The directory \"%arg0\"\n is not existing. Please check and retry."; table["NANA_FILEBOX_ERROR_DIRECTORY_INVALID"] = "The directory \"%arg0\"\n is invalid. Please check and retry."; table["NANA_FILEBOX_ERROR_QUERY_REWRITE_BECAUSE_OF_EXISTING"] = "The input file is existing, do you want to overwrite it?"; } }; static std::shared_ptr& get_data_ptr() { static std::shared_ptr data_ptr; if (!data_ptr) data_ptr = std::make_shared(); return data_ptr; } void load(const std::string& file, bool utf8) { auto impl = std::make_shared(); tokenizer tknizer(file, utf8); while (true) { if (token::msgid != tknizer.read()) break; if (token::string != tknizer.read()) return; std::string msgid = std::move(tknizer.get_str()); if (!utf8) msgid = nana::charset(std::move(msgid)).to_bytes(nana::unicode::utf8); if (token::msgstr != tknizer.read()) return; if (token::string != tknizer.read()) return; std::string str; if (utf8) str = tknizer.get_str(); else str = nana::charset(std::move(tknizer.get_str())).to_bytes(nana::unicode::utf8); std::string::size_type pos = 0; while (true) { pos = str.find('\\', pos); if (pos == str.npos) break; if (pos + 1 < str.size()) { auto ch = str[pos + 1]; switch (ch) { case '"': case '\\': str.erase(pos, 1); break; case 'n': str.erase(pos, 1); str[pos] = '\n'; break; case 't': str.erase(pos, 1); str[pos] = '\t'; break; } ++pos; } else break; } impl->table[std::move(msgid)].swap(str); } //Assign all language texts to the new table. auto & cur_table = get_data_ptr()->table; auto & new_table = impl->table; for (auto & m : cur_table) { auto & value = new_table[m.first]; if (value.empty()) value = m.second; } get_data_ptr().swap(impl); use_eval(); } struct eval_window { nana::event_handle destroy{nullptr}; i18n_eval eval; eval_window() = default; eval_window(i18n_eval&& arg) : eval(std::move(arg)) {} }; struct eval_manager { std::recursive_mutex mutex; std::map table; }; eval_manager& get_eval_manager() { static eval_manager evals; return evals; } void set_eval(nana::window wd, i18n_eval&& eval) { auto & mgr = get_eval_manager(); std::lock_guard lock(mgr.mutex); auto i = mgr.table.find(wd); if (i == mgr.table.end()) { auto result = mgr.table.emplace(wd, std::move(eval)); result.first->second.destroy = nana::API::events(wd).destroy.connect([wd](const arg_destroy&){ auto & eval_mgr = get_eval_manager(); std::lock_guard lockgd(eval_mgr.mutex); eval_mgr.table.erase(wd); }); } else i->second.eval = std::move(eval); } void use_eval() { auto & mgr = get_eval_manager(); std::lock_guard lock(mgr.mutex); for (auto & eval : mgr.table) { nana::API::window_caption(eval.first, eval.second.eval()); } } }//end namespace internationalization_parts void internationalization::set_missing(std::function handler) { internationalization_parts::get_data_ptr()->on_missing = std::move(handler); } void internationalization::load(const std::string& file) { internationalization_parts::load(file, false); } void internationalization::load_utf8(const std::string& file) { internationalization_parts::load(file, true); } std::string internationalization::get(std::string msgid) const { std::string str = _m_get(std::move(msgid)); _m_replace_args(str, nullptr); return str; } void internationalization::set(std::string msgid, std::string msgstr) { auto & ptr = internationalization_parts::get_data_ptr(); ptr->table[msgid].swap(msgstr); } std::string internationalization::_m_get(std::string&& msgid) const { auto & impl = internationalization_parts::get_data_ptr(); auto i = impl->table.find(msgid); if (i != impl->table.end()) return i->second; if (impl->on_missing) impl->on_missing(msgid); return std::move(msgid); } void internationalization::_m_replace_args(std::string& str, std::vector * arg_strs) const { std::string::size_type offset = 0; while (true) { auto pos = str.find("%arg", offset); if (pos == str.npos) break; offset = pos; pos = str.find_first_not_of("0123456789", offset + 4); if ((pos == str.npos) || (pos != offset + 4)) { std::string::size_type erase_n = 0; std::string::size_type arg_n = str.npos; if (pos != str.npos) { erase_n = pos - offset; arg_n = pos - offset - 4; } else erase_n = str.size() - offset; //If there is not a parameter for %argNNN, the %argNNN will be erased. std::size_t arg = static_cast(std::stoi(str.substr(offset + 4, arg_n))); if (arg_strs && arg < arg_strs->size()) str.replace(offset, erase_n, (*arg_strs)[arg]); else str.erase(offset, erase_n); } else offset += 4; } } #ifndef __cpp_fold_expressions void internationalization::_m_fetch_args(std::vector&) {} #endif void internationalization::_m_fetch_args(std::vector& v, const char* arg) { v.emplace_back(arg); } void internationalization::_m_fetch_args(std::vector& v, const std::string& arg) { v.emplace_back(arg); } void internationalization::_m_fetch_args(std::vector& v, std::string& arg) { v.emplace_back(arg); } void internationalization::_m_fetch_args(std::vector& v, std::string&& arg) { v.emplace_back(std::move(arg)); } void internationalization::_m_fetch_args(std::vector& v, const wchar_t* arg) { v.emplace_back(to_utf8(arg)); } void internationalization::_m_fetch_args(std::vector& v, const std::wstring& arg) { v.emplace_back(to_utf8(arg)); } void internationalization::_m_fetch_args(std::vector& v, std::wstring& arg) { v.emplace_back(to_utf8(arg)); } void internationalization::_m_fetch_args(std::vector& v, std::wstring&& arg) { v.emplace_back(to_utf8(arg)); } //end class internationalization class i18n_eval::arg_string : public eval_arg { public: arg_string(std::string str) : str_(std::move(str)) {} std::string eval() const override { return str_; } std::unique_ptr clone() const override { return std::unique_ptr(new arg_string(str_)); } private: std::string str_; }; class i18n_eval::arg_eval : public eval_arg { public: arg_eval(const i18n_eval& eval) : eval_{ eval } {} arg_eval(i18n_eval&& eval) : eval_{ std::move(eval) } {} std::string eval() const override { return eval_(); } std::unique_ptr clone() const override { return std::unique_ptr(new arg_eval(eval_)); } private: i18n_eval eval_; }; //class i18n_eval i18n_eval::i18n_eval(const i18n_eval& rhs) : msgid_(rhs.msgid_) { for (auto & arg : rhs.args_) args_.emplace_back(arg->clone()); } //Workaround for VC2013, because it can't specify a default explicit move-constructor i18n_eval::i18n_eval(i18n_eval&& other) : msgid_(std::move(other.msgid_)), args_(std::move(other.args_)) { } i18n_eval& i18n_eval::operator=(const i18n_eval& rhs) { if (this != &rhs) { msgid_ = rhs.msgid_; for (auto & arg : rhs.args_) args_.emplace_back(arg->clone()); } return *this; } i18n_eval& i18n_eval::operator=(i18n_eval&& rhs) { if (this != &rhs) { msgid_ = std::move(rhs.msgid_); args_ = std::move(rhs.args_); } return *this; } std::string i18n_eval::operator()() const { if (msgid_.empty()) return{}; std::vector arg_strs; for (auto & arg : args_) arg_strs.emplace_back(arg->eval()); internationalization i18n; std::string msgstr = i18n._m_get(std::string{msgid_}); i18n._m_replace_args(msgstr, &arg_strs); return msgstr; } void i18n_eval::_m_add_args(i18n_eval& eval) { args_.emplace_back(new arg_eval(eval)); } void i18n_eval::_m_add_args(const i18n_eval& eval) { args_.emplace_back(new arg_eval(eval)); } void i18n_eval::_m_add_args(i18n_eval&& eval) { args_.emplace_back(new arg_eval(std::move(eval))); } void i18n_eval::_m_add_args(std::string& str) { args_.emplace_back(new arg_string(str)); } void i18n_eval::_m_add_args(const std::string& str) { args_.emplace_back(new arg_string(str)); } void i18n_eval::_m_add_args(std::string&& str) { args_.emplace_back(new arg_string(std::move(str))); } void i18n_eval::_m_add_args(const std::wstring& str) { args_.emplace_back(new arg_string(to_utf8(str))); } //end class i18n_eval } #include