mirror of
https://github.com/sipeed/MaixCDK.git
synced 2026-09-10 21:59:54 -05:00
update i18n support load from yaml files, add trans search tool maixtool i18n command
This commit is contained in:
@@ -29,7 +29,7 @@ endif()
|
||||
|
||||
|
||||
###### Add required/dependent components ######
|
||||
list(APPEND ADD_REQUIREMENTS pthread stdc++fs ini)
|
||||
list(APPEND ADD_REQUIREMENTS pthread stdc++fs ini yaml)
|
||||
###############################################
|
||||
|
||||
###### Add link search path for requirements/libs ######
|
||||
|
||||
@@ -168,10 +168,10 @@ namespace maix::fs
|
||||
/**
|
||||
* Get file extension
|
||||
* @param path path to get extension
|
||||
* @return extension if success, empty string if failed
|
||||
* @return prefix_path and extension list if success, empty string if failed
|
||||
* @maixpy maix.fs.splitext
|
||||
*/
|
||||
std::string splitext(const std::string &path);
|
||||
std::vector<std::string> splitext(const std::string &path);
|
||||
|
||||
/**
|
||||
* List files in directory
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "maix_err.hpp"
|
||||
#include "maix_log.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -18,43 +20,58 @@ namespace maix::i18n
|
||||
/**
|
||||
* i18n locales list
|
||||
* @maixpy maix.i18n.locales
|
||||
*/
|
||||
*/
|
||||
static std::vector<std::string> locales = {
|
||||
"en",
|
||||
"zh",
|
||||
"zh-tw",
|
||||
"ja"
|
||||
};
|
||||
"ja"};
|
||||
|
||||
/**
|
||||
* i18n language names list
|
||||
* @maixpy maix.i18n.names
|
||||
*/
|
||||
*/
|
||||
const static std::vector<std::string> names = {
|
||||
"English",
|
||||
"简体中文",
|
||||
"繁體中文",
|
||||
"日本語"
|
||||
};
|
||||
"日本語"};
|
||||
|
||||
/**
|
||||
* Get system config of locale.
|
||||
* @return language locale, e.g. en, zh, zh_CN, zh_TW, etc.
|
||||
* @maixpy maix.i18n.get_locale
|
||||
*/
|
||||
*/
|
||||
string get_locale();
|
||||
|
||||
/**
|
||||
* Get system config of language name.
|
||||
* @return language name, e.g. English, 简体中文, 繁體中文, etc.
|
||||
* @maixpy maix.i18n.get_language_name
|
||||
*/
|
||||
*/
|
||||
string get_language_name();
|
||||
|
||||
/**
|
||||
* Load translations from yaml files.
|
||||
* @param locales_dir translation yaml files directory.
|
||||
* @return A dict contains all translations, e.g. {"zh":{"hello": "你好"}, "en":{"hello": "hello"}}, you should delete it after use in C++.
|
||||
* @maixpy maix.i18n.load_trans_yaml
|
||||
*/
|
||||
const std::map<string, std::map<string, string>> *load_trans_yaml(const std::string &locales_dir);
|
||||
|
||||
/**
|
||||
* Load translations from yaml files.
|
||||
* @param locales_dir translation yaml files directory.
|
||||
* @param dict dict to store key values. A dict contains all translations, e.g. {"zh":{"hello": "你好"}, "en":{"hello": "hello"}}, you should delete it after use in C++.
|
||||
* @return err::ERR
|
||||
* @maixcdk maix.i18n.load_trans_yaml
|
||||
*/
|
||||
err::Err load_trans_yaml(const std::string &locales_dir, std::map<string, std::map<string, string>> &dict);
|
||||
|
||||
/**
|
||||
* Translate helper class.
|
||||
* @maixpy maix.i18n.Trans
|
||||
*/
|
||||
*/
|
||||
class Trans
|
||||
{
|
||||
public:
|
||||
@@ -65,27 +82,81 @@ namespace maix::i18n
|
||||
* @param locales_dict locales dict, e.g. {"zh": {"Confirm": "确认", "OK": "好的"}, "en": {"Confirm": "Confirm", "OK": "OK"}}
|
||||
* @maixpy maix.i18n.Trans.__init__
|
||||
* @maixcdk maix.i18n.Trans.Trans
|
||||
*/
|
||||
Trans(const std::map<string, const std::map<string, string>> &locales_dict)
|
||||
:locales_dict(locales_dict)
|
||||
*/
|
||||
Trans(const std::map<string, const std::map<string, string>> &locales_dict = std::map<string, const std::map<string, string>>())
|
||||
: locales_dict_const(locales_dict)
|
||||
{
|
||||
this->locale = ""; // DO NOT load from file system here, will coredump
|
||||
}
|
||||
|
||||
/**
|
||||
* Load translation from yaml files generated by `maixtool i18n` command.
|
||||
* @param locales_dir the translation files directory.
|
||||
* @return err.Err type, no error will return err.Err.ERR_NONE.
|
||||
* @maixpy maix.i18n.Trans.load
|
||||
*/
|
||||
err::Err load(const std::string &locales_dir)
|
||||
{
|
||||
return load_trans_yaml(locales_dir, locales_dict);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update translation dict.
|
||||
* @param dict the new translation dict.
|
||||
* @return err.Err type, no error will return err.Err.ERR_NONE.
|
||||
* @maixpy maix.i18n.Trans.update_dict
|
||||
*/
|
||||
err::Err update_dict(const std::map<std::string, const std::map<std::string, std::string>> &dict)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (const auto &file_pair : dict)
|
||||
{
|
||||
const std::string &filename = file_pair.first;
|
||||
for (const auto &kv_pair : file_pair.second)
|
||||
{
|
||||
locales_dict[filename][kv_pair.first] = kv_pair.second;
|
||||
}
|
||||
}
|
||||
return err::Err();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
log::error("copy dict value failed");
|
||||
return err::Err::ERR_ARGS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate string by key.
|
||||
* @param key string key, e.g. "Confirm"
|
||||
* @param locale locale name, if not assign, use default locale set by system settings or set_locale function.
|
||||
* @return translated string, if find translation, return it, or return key, e.g. "确认", "Confirm", etc.
|
||||
* @maixpy maix.i18n.Trans.tr
|
||||
*/
|
||||
*/
|
||||
string tr(const string &key, const string locale = "")
|
||||
{
|
||||
if(this->locale.empty())
|
||||
if (this->locale.empty())
|
||||
this->locale = i18n::get_locale();
|
||||
// if locale not in locales_dict, return key
|
||||
const std::map<string, const std::map<string, string>>::const_iterator iter = locales_dict.find(locale.empty() ? this->locale : locale);
|
||||
if (iter == locales_dict.end())
|
||||
if (!locales_dict.empty())
|
||||
{
|
||||
const std::map<string, std::map<string, string>>::const_iterator iter = locales_dict.find(locale.empty() ? this->locale : locale);
|
||||
if (iter == locales_dict.end())
|
||||
{
|
||||
return key;
|
||||
}
|
||||
// get key value from locales_dict[locale][key], default value is key
|
||||
const std::map<string, string> dict = iter->second;
|
||||
const std::map<string, string>::const_iterator iter2 = dict.find(key);
|
||||
if (iter2 == dict.end())
|
||||
{
|
||||
return key;
|
||||
}
|
||||
return iter2->second;
|
||||
}
|
||||
const std::map<string, const std::map<string, string>>::const_iterator iter = locales_dict_const.find(locale.empty() ? this->locale : locale);
|
||||
if (iter == locales_dict_const.end())
|
||||
{
|
||||
return key;
|
||||
}
|
||||
@@ -103,7 +174,7 @@ namespace maix::i18n
|
||||
* Set locale temporarily, will not affect system settings.
|
||||
* @param locale locale name, e.g. "zh", "en", etc. @see maix.i18n.locales
|
||||
* @maixpy maix.i18n.Trans.set_locale
|
||||
*/
|
||||
*/
|
||||
void set_locale(const string &locale)
|
||||
{
|
||||
this->locale = locale;
|
||||
@@ -113,16 +184,17 @@ namespace maix::i18n
|
||||
* Get current locale.
|
||||
* @return locale name, e.g. "zh", "en", etc. @see maix.i18n.locales
|
||||
* @maixpy maix.i18n.Trans.get_locale
|
||||
*/
|
||||
*/
|
||||
string get_locale()
|
||||
{
|
||||
if(this->locale.empty())
|
||||
if (this->locale.empty())
|
||||
this->locale = i18n::get_locale();
|
||||
return this->locale;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<string, const std::map<string, string>> locales_dict; // copy dict to here to avoid memory problem, e.g. For python
|
||||
std::map<string, std::map<string, string>> locales_dict; // copy dict to here to avoid memory problem, e.g. For python
|
||||
std::map<string, const std::map<string, string>> locales_dict_const;
|
||||
string locale;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,27 +48,35 @@ namespace maix::fs
|
||||
err::Err symlink(const std::string &src, const std::string &link, bool force)
|
||||
{
|
||||
// 检查源文件是否存在
|
||||
if (!fs::exists(src)) {
|
||||
if (!fs::exists(src))
|
||||
{
|
||||
return err::Err::ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 删除已存在的软链接
|
||||
if (fs::exists(link)) {
|
||||
if(!force)
|
||||
if (fs::exists(link))
|
||||
{
|
||||
if (!force)
|
||||
{
|
||||
return err::Err::ERR_ALREAY_EXIST;
|
||||
}
|
||||
try {
|
||||
try
|
||||
{
|
||||
fs::remove(link);
|
||||
} catch(...) {
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return err::Err::ERR_IO;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建软链接
|
||||
try {
|
||||
try
|
||||
{
|
||||
fs_sys::create_symlink(src, link);
|
||||
} catch(...) {
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return err::Err::ERR_IO;
|
||||
}
|
||||
return err::Err::ERR_NONE;
|
||||
@@ -84,7 +92,7 @@ namespace maix::fs
|
||||
{
|
||||
// create a directory use fs_sys::create_directories
|
||||
// if exist_ok is true, also return true if directory already exists
|
||||
if(!exist_ok && fs_sys::exists(path))
|
||||
if (!exist_ok && fs_sys::exists(path))
|
||||
{
|
||||
return err::ERR_ALREAY_EXIST;
|
||||
}
|
||||
@@ -103,7 +111,7 @@ namespace maix::fs
|
||||
err::Err rmdir(const std::string &path, bool recursive)
|
||||
{
|
||||
// remove a directory use fs_sys::remove_all
|
||||
if(!fs_sys::exists(path))
|
||||
if (!fs_sys::exists(path))
|
||||
{
|
||||
return err::ERR_NOT_FOUND;
|
||||
}
|
||||
@@ -122,7 +130,7 @@ namespace maix::fs
|
||||
err::Err remove(const std::string &path)
|
||||
{
|
||||
// remove a file use fs_sys::remove
|
||||
if(!fs_sys::exists(path))
|
||||
if (!fs_sys::exists(path))
|
||||
{
|
||||
return err::ERR_NOT_FOUND;
|
||||
}
|
||||
@@ -133,7 +141,7 @@ namespace maix::fs
|
||||
err::Err rename(const std::string &src, const std::string &dst)
|
||||
{
|
||||
// rename a file or directory use fs_sys::rename
|
||||
if(!fs_sys::exists(src))
|
||||
if (!fs_sys::exists(src))
|
||||
{
|
||||
return err::ERR_NOT_FOUND;
|
||||
}
|
||||
@@ -149,7 +157,7 @@ namespace maix::fs
|
||||
int getsize(const std::string &path)
|
||||
{
|
||||
// get file size use fs_sys::file_size
|
||||
if(!fs_sys::exists(path))
|
||||
if (!fs_sys::exists(path))
|
||||
{
|
||||
return -err::ERR_NOT_FOUND;
|
||||
}
|
||||
@@ -160,7 +168,7 @@ namespace maix::fs
|
||||
{
|
||||
fs_sys::path p(path);
|
||||
std::string ret = p.parent_path().string();
|
||||
if(ret.empty())
|
||||
if (ret.empty())
|
||||
{
|
||||
ret = ".";
|
||||
}
|
||||
@@ -188,23 +196,38 @@ namespace maix::fs
|
||||
return fs_sys::canonical(path).string();
|
||||
}
|
||||
|
||||
std::string splitext(const std::string &path)
|
||||
std::vector<std::string> splitext(const std::string &path)
|
||||
{
|
||||
fs_sys::path p(path);
|
||||
return p.extension().string();
|
||||
std::vector<std::string> result;
|
||||
|
||||
// 获取文件的后缀名
|
||||
std::string extension = p.extension().string();
|
||||
|
||||
// 获取文件的前缀
|
||||
std::string stem = p.stem().string();
|
||||
std::string parent_path = p.parent_path().string();
|
||||
|
||||
// 拼接前缀路径和文件名
|
||||
std::string prefix = parent_path.empty() ? stem : parent_path + fs_sys::path::preferred_separator + stem;
|
||||
|
||||
result.push_back(prefix);
|
||||
result.push_back(extension);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> *listdir(const std::string &path, bool recursive, bool full_path)
|
||||
{
|
||||
// list directory use fs_sys::directory_iterator
|
||||
if(!fs_sys::exists(path))
|
||||
if (!fs_sys::exists(path))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<std::string> *list = new std::vector<std::string>();
|
||||
if (recursive)
|
||||
{
|
||||
if(full_path)
|
||||
if (full_path)
|
||||
for (auto &p : fs_sys::recursive_directory_iterator(path))
|
||||
{
|
||||
list->push_back(p.path().string());
|
||||
@@ -217,7 +240,7 @@ namespace maix::fs
|
||||
}
|
||||
else
|
||||
{
|
||||
if(full_path)
|
||||
if (full_path)
|
||||
for (auto &p : fs_sys::directory_iterator(path))
|
||||
{
|
||||
list->push_back(p.path().string());
|
||||
@@ -236,7 +259,7 @@ namespace maix::fs
|
||||
err::Err error = err::ERR_NONE;
|
||||
fs::File *file = new fs::File();
|
||||
error = file->open(path, mode);
|
||||
if(error != err::ERR_NONE)
|
||||
if (error != err::ERR_NONE)
|
||||
{
|
||||
log::error("open file %s failed, error code: %d\n", path.c_str(), error);
|
||||
delete file;
|
||||
@@ -250,16 +273,15 @@ namespace maix::fs
|
||||
return fs_sys::temp_directory_path().string();
|
||||
}
|
||||
|
||||
|
||||
err::Err File::open(const std::string &path, const std::string &mode)
|
||||
{
|
||||
// open file use std::fopen
|
||||
if(_fp != nullptr)
|
||||
if (_fp != nullptr)
|
||||
{
|
||||
return err::ERR_NOT_READY;
|
||||
}
|
||||
_fp = std::fopen(path.c_str(), mode.c_str());
|
||||
if(_fp == nullptr)
|
||||
if (_fp == nullptr)
|
||||
{
|
||||
log::error("open file %s failed\n", path.c_str());
|
||||
return err::ERR_ARGS;
|
||||
@@ -270,9 +292,9 @@ namespace maix::fs
|
||||
void File::close()
|
||||
{
|
||||
// close file use std::fclose
|
||||
if(_fp != nullptr)
|
||||
if (_fp != nullptr)
|
||||
{
|
||||
std::fclose((FILE*)_fp);
|
||||
std::fclose((FILE *)_fp);
|
||||
_fp = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -280,24 +302,24 @@ namespace maix::fs
|
||||
int File::read(void *buf, int size)
|
||||
{
|
||||
// read data from file use std::fread
|
||||
if(_fp == nullptr)
|
||||
if (_fp == nullptr)
|
||||
{
|
||||
return -err::ERR_NOT_READY;
|
||||
}
|
||||
return std::fread(buf, 1, size, (FILE*)_fp);
|
||||
return std::fread(buf, 1, size, (FILE *)_fp);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> *File::read(int size)
|
||||
{
|
||||
// read data from file use std::fread
|
||||
if(_fp == nullptr)
|
||||
if (_fp == nullptr)
|
||||
{
|
||||
log::error("file not opened\n");
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<uint8_t> *buf = new std::vector<uint8_t>(size);
|
||||
int read_size = std::fread(buf->data(), 1, size, (FILE*)_fp);
|
||||
if(read_size < 0)
|
||||
int read_size = std::fread(buf->data(), 1, size, (FILE *)_fp);
|
||||
if (read_size < 0)
|
||||
{
|
||||
delete buf;
|
||||
return nullptr;
|
||||
@@ -309,12 +331,12 @@ namespace maix::fs
|
||||
int File::readline(std::string &line)
|
||||
{
|
||||
// read line from file use std::fgets
|
||||
if(_fp == nullptr)
|
||||
if (_fp == nullptr)
|
||||
{
|
||||
return -err::ERR_NOT_OPEN;
|
||||
}
|
||||
char buf[1024] = {0};
|
||||
if(std::fgets(buf, 1024, (FILE*)_fp) == nullptr)
|
||||
if (std::fgets(buf, 1024, (FILE *)_fp) == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -325,12 +347,12 @@ namespace maix::fs
|
||||
std::string *File::readline()
|
||||
{
|
||||
// read line from file use std::fgets
|
||||
if(_fp == nullptr)
|
||||
if (_fp == nullptr)
|
||||
{
|
||||
throw err::Exception(err::ERR_NOT_OPEN, "file not opened");
|
||||
}
|
||||
char buf[1024] = {0};
|
||||
if(std::fgets(buf, 1024, (FILE*)_fp) == nullptr)
|
||||
if (std::fgets(buf, 1024, (FILE *)_fp) == nullptr)
|
||||
{
|
||||
return new std::string();
|
||||
}
|
||||
@@ -339,65 +361,64 @@ namespace maix::fs
|
||||
}
|
||||
|
||||
/**
|
||||
* End of file or not
|
||||
* @return 0 if not reach end of file, else eof.
|
||||
* @maixpy maix.fs.File.eof
|
||||
*/
|
||||
int File::eof()
|
||||
{
|
||||
return std::feof((FILE*)_fp);
|
||||
}
|
||||
* End of file or not
|
||||
* @return 0 if not reach end of file, else eof.
|
||||
* @maixpy maix.fs.File.eof
|
||||
*/
|
||||
int File::eof()
|
||||
{
|
||||
return std::feof((FILE *)_fp);
|
||||
}
|
||||
|
||||
int File::write(const void *buf, int size)
|
||||
{
|
||||
// write data to file use std::fwrite
|
||||
if(_fp == nullptr)
|
||||
if (_fp == nullptr)
|
||||
{
|
||||
return -err::ERR_NOT_READY;
|
||||
}
|
||||
return std::fwrite(buf, 1, size, (FILE*)_fp);
|
||||
return std::fwrite(buf, 1, size, (FILE *)_fp);
|
||||
}
|
||||
|
||||
int File::write(const std::vector<uint8_t> &buf)
|
||||
{
|
||||
// write data to file use std::fwrite
|
||||
if(_fp == nullptr)
|
||||
if (_fp == nullptr)
|
||||
{
|
||||
return -err::ERR_NOT_READY;
|
||||
}
|
||||
return std::fwrite(buf.data(), 1, buf.size(), (FILE*)_fp);
|
||||
return std::fwrite(buf.data(), 1, buf.size(), (FILE *)_fp);
|
||||
}
|
||||
|
||||
int File::seek(int offset, int whence)
|
||||
{
|
||||
// seek file position use std::fseek
|
||||
if(_fp == nullptr)
|
||||
if (_fp == nullptr)
|
||||
{
|
||||
return -err::ERR_NOT_READY;
|
||||
}
|
||||
return std::fseek((FILE*)_fp, offset, whence);
|
||||
return std::fseek((FILE *)_fp, offset, whence);
|
||||
}
|
||||
|
||||
int File::tell()
|
||||
{
|
||||
// get file position use std::ftell
|
||||
if(_fp == nullptr)
|
||||
if (_fp == nullptr)
|
||||
{
|
||||
return -err::ERR_NOT_READY;
|
||||
}
|
||||
return std::ftell((FILE*)_fp);
|
||||
return std::ftell((FILE *)_fp);
|
||||
}
|
||||
|
||||
err::Err File::flush()
|
||||
{
|
||||
// flush file use std::fflush
|
||||
if(_fp == nullptr)
|
||||
if (_fp == nullptr)
|
||||
{
|
||||
return err::ERR_NOT_READY;
|
||||
}
|
||||
std::fflush((FILE*)_fp);
|
||||
std::fflush((FILE *)_fp);
|
||||
return err::ERR_NONE;
|
||||
}
|
||||
|
||||
} // namespace maix::fs
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include "maix_i18n.hpp"
|
||||
#include "maix_app.hpp"
|
||||
#include <yaml-cpp/yaml.h>
|
||||
#include "maix_fs.hpp"
|
||||
|
||||
namespace maix::i18n
|
||||
{
|
||||
@@ -33,4 +35,69 @@ namespace maix::i18n
|
||||
return "English";
|
||||
}
|
||||
|
||||
const std::map<std::string, std::map<std::string, std::string>> *load_trans_yaml(const std::string &locales_dir)
|
||||
{
|
||||
auto *translations = new std::map<std::string, std::map<std::string, std::string>>();
|
||||
|
||||
// 遍历目录中的所有文件
|
||||
std::vector<std::string> *files = fs::listdir(locales_dir, true, true);
|
||||
for (std::string file : *files)
|
||||
{
|
||||
std::string filename = fs::basename(file);
|
||||
std::vector<std::string> splitname = fs::splitext(file);
|
||||
if (splitname[1] == ".yaml")
|
||||
{
|
||||
YAML::Node node = YAML::LoadFile(file);
|
||||
std::map<std::string, std::string> content;
|
||||
|
||||
// 遍历 YAML 文件中的所有键值对
|
||||
for (YAML::const_iterator it = node.begin(); it != node.end(); ++it)
|
||||
{
|
||||
content[it->first.as<std::string>()] = it->second.as<std::string>();
|
||||
}
|
||||
|
||||
(*translations)[splitname[0]] = content;
|
||||
}
|
||||
}
|
||||
delete files;
|
||||
return translations;
|
||||
}
|
||||
|
||||
err::Err load_trans_yaml(const std::string &locales_dir, std::map<string, std::map<string, string>> &dict)
|
||||
{
|
||||
if(!fs::exists(locales_dir))
|
||||
{
|
||||
log::error("dir [ %s ] not found", locales_dir.c_str());
|
||||
return err::ERR_ARGS;
|
||||
}
|
||||
|
||||
// 遍历目录中的所有文件
|
||||
std::vector<std::string> *files = fs::listdir(locales_dir, true, true);
|
||||
if(!files)
|
||||
{
|
||||
log::error("no trans yaml files");
|
||||
return err::ERR_ARGS;
|
||||
}
|
||||
for (std::string file : *files)
|
||||
{
|
||||
std::string filename = fs::basename(file);
|
||||
std::vector<std::string> splitname = fs::splitext(filename);
|
||||
if (splitname[1] == ".yaml")
|
||||
{
|
||||
YAML::Node node = YAML::LoadFile(file);
|
||||
std::map<std::string, std::string> content;
|
||||
|
||||
// 遍历 YAML 文件中的所有键值对
|
||||
for (YAML::const_iterator it = node.begin(); it != node.end(); ++it)
|
||||
{
|
||||
content[it->first.as<std::string>()] = it->second.as<std::string>();
|
||||
}
|
||||
|
||||
dict[splitname[0]] = content;
|
||||
}
|
||||
}
|
||||
delete files;
|
||||
return err::ERR_NONE;
|
||||
}
|
||||
|
||||
} // namespace maix::i18n
|
||||
|
||||
12
examples/i18n/README.md
Normal file
12
examples/i18n/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
MaixCDK i18n demo
|
||||
=====
|
||||
|
||||
## Usage
|
||||
|
||||
1. coding like [main.cpp](./main/src/main.cpp), use `tr` function to call the string you want to translate.
|
||||
2. Execute `maixtool i18n -d . -r` in project dir to generate translation files, they will be in `locales` dir, all are `yaml` file.
|
||||
3. Translate files.
|
||||
4. Compile and you will get binary files in dist dir, copy them to board and run.
|
||||
|
||||
|
||||
|
||||
9
examples/i18n/app.yaml
Normal file
9
examples/i18n/app.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
id: i18n_demo
|
||||
name: I18N demo
|
||||
version: 1.0.0
|
||||
#icon: assets/hello.png
|
||||
author: Sipeed Ltd
|
||||
desc: i18n demo
|
||||
files:
|
||||
- locales
|
||||
|
||||
2
examples/i18n/locales/en.yaml
Normal file
2
examples/i18n/locales/en.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
hello: hello
|
||||
out: out
|
||||
2
examples/i18n/locales/zh.yaml
Normal file
2
examples/i18n/locales/zh.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
hello: 你好
|
||||
out: 出去
|
||||
@@ -4,37 +4,43 @@
|
||||
|
||||
using namespace maix;
|
||||
|
||||
const std::map<string, string> locale_zh_dict = {
|
||||
{"out", "输出"},
|
||||
{"hello", "你好"}
|
||||
};
|
||||
// const std::map<string, string> locale_zh_dict = {
|
||||
// {"out", "输出"},
|
||||
// {"hello", "你好"}
|
||||
// };
|
||||
|
||||
const std::map<string, string> locale_ja_dict = {
|
||||
// {"out", "出力"},
|
||||
{"hello", "こんにちは"}
|
||||
};
|
||||
// const std::map<string, string> locale_ja_dict = {
|
||||
// // {"out", "出力"},
|
||||
// {"hello", "こんにちは"}
|
||||
// };
|
||||
|
||||
const std::map<string, const std::map<string, string>> locales_dict = {
|
||||
{"zh", locale_zh_dict},
|
||||
{"ja", locale_ja_dict}
|
||||
};
|
||||
// const std::map<string, const std::map<string, string>> locales_dict = {
|
||||
// {"zh", locale_zh_dict},
|
||||
// {"ja", locale_ja_dict}
|
||||
// };
|
||||
|
||||
|
||||
i18n::Trans trans(locales_dict);
|
||||
// i18n::Trans trans(locales_dict);
|
||||
i18n::Trans trans;
|
||||
|
||||
int _main(int argc, char* argv[])
|
||||
{
|
||||
err::Err e = trans.load("locales");
|
||||
err::check_raise(e, "load translation yamls failed");
|
||||
|
||||
log::info("system locale: %s\n", i18n::get_locale().c_str());
|
||||
log::info("%s: %s\n", trans.tr("out").c_str(), trans.tr("hello").c_str());
|
||||
log::info("%s: %s, %s\n", i18n::get_locale().c_str(), trans.tr("out").c_str(), trans.tr("hello").c_str());
|
||||
|
||||
trans.set_locale("zh");
|
||||
log::info("%s: %s\n", trans.tr("out").c_str(), trans.tr("hello").c_str());
|
||||
log::info("zh: %s, %s\n", trans.tr("out").c_str(), trans.tr("hello").c_str());
|
||||
|
||||
trans.set_locale("en");
|
||||
log::info("%s: %s\n", trans.tr("out").c_str(), trans.tr("hello").c_str());
|
||||
log::info("en: %s, %s\n", trans.tr("out").c_str(), trans.tr("hello").c_str());
|
||||
|
||||
trans.set_locale("ja");
|
||||
log::info("%s: %s\n", trans.tr("out").c_str(), trans.tr("hello").c_str());
|
||||
log::info("ja: %s, %s\n", trans.tr("out").c_str(), trans.tr("hello").c_str());
|
||||
|
||||
// after coding, you need to execute `maixtool i18n -d . -r` in project dir to generate translation files, and translate them
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
49
tools/cmake/copy_assets.py
Normal file
49
tools/cmake/copy_assets.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import yaml
|
||||
|
||||
def copy_assets(project_path, dist_path):
|
||||
app_yaml_path = os.path.join(project_path, 'app.yaml')
|
||||
|
||||
# 检查 app.yaml 是否存在
|
||||
if not os.path.exists(app_yaml_path):
|
||||
print(f"{app_yaml_path} not a app, skip copy files")
|
||||
return
|
||||
|
||||
# 读取 app.yaml 文件
|
||||
with open(app_yaml_path, 'r') as file:
|
||||
config = yaml.safe_load(file)
|
||||
|
||||
# 检查 assets 键是否存在
|
||||
if 'files' not in config:
|
||||
print("No 'files' key found in app.yaml, skip copy files")
|
||||
return
|
||||
|
||||
assets = config.get('files', [])
|
||||
|
||||
if isinstance(assets, list):
|
||||
for item in assets:
|
||||
src = os.path.join(project_path, item)
|
||||
dest = os.path.join(dist_path, item)
|
||||
if os.path.exists(src):
|
||||
shutil.copytree(src, dest, dirs_exist_ok=True)
|
||||
print(f"Copied {src} to {dest}")
|
||||
else:
|
||||
raise Exception(f"{src} does not exist.")
|
||||
elif isinstance(assets, dict):
|
||||
for src_key, dest_value in assets.items():
|
||||
src = os.path.join(project_path, src_key)
|
||||
dest = os.path.join(dist_path, dest_value)
|
||||
if os.path.exists(src):
|
||||
shutil.copytree(src, dest, dirs_exist_ok=True)
|
||||
print(f"Copied {src} to {dest}")
|
||||
else:
|
||||
raise Exception(f"{src} does not exist.")
|
||||
else:
|
||||
print("'files' key must be a list or a dictionary.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
project_path = sys.argv[1]
|
||||
dist_path = sys.argv[2]
|
||||
copy_assets(project_path, dist_path)
|
||||
@@ -25,12 +25,15 @@ if(${BUILD_TYPE} STREQUAL "Release")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(cp_assets_cmd COMMAND python ${SDK_PATH}/tools/cmake/copy_assets.py ${PROJECT_PATH} ${PROJECT_DIST_DIR}/${PROJECT_ID}_${build_type})
|
||||
|
||||
add_custom_command(TARGET ${PROJECT_ID} POST_BUILD
|
||||
${strip_cmd}
|
||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/dl_lib
|
||||
${cp_command}
|
||||
${cp_dist_cmd}
|
||||
${cp_dl_to_dist_cmd}
|
||||
${cp_assets_cmd}
|
||||
DEPENDS ${PROJECT_ID}
|
||||
COMMENT "-- copy dynamic libs to build/dl_lib dir ...")
|
||||
|
||||
|
||||
101
tools/maixtool/maixtool/i18n.py
Normal file
101
tools/maixtool/maixtool/i18n.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import sys, os
|
||||
import yaml
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
def get_files(scan_dir, recursive, exts=[".c", ".cpp", ".h", ".hpp"]):
|
||||
files = []
|
||||
if recursive:
|
||||
for root, dirs, fs in os.walk(scan_dir):
|
||||
for f in fs:
|
||||
if os.path.splitext(f)[-1] in exts:
|
||||
files.append(os.path.join(root, f))
|
||||
else:
|
||||
for name in os.listdir(scan_dir):
|
||||
if os.path.splitext(name)[1] in exts:
|
||||
files.append(os.path.join(scan_dir, name))
|
||||
return files
|
||||
|
||||
|
||||
def get_i18n_strs(file, keywords):
|
||||
'''
|
||||
read content and find '_("xxx")', get all xxx and return
|
||||
'''
|
||||
strs = []
|
||||
with open(file, "r") as f:
|
||||
content = f.read()
|
||||
for key in keywords:
|
||||
idx = 0
|
||||
while True:
|
||||
flag = False
|
||||
idx = content.find(f'{key}("', idx)
|
||||
if idx == -1:
|
||||
idx = content.find(f"{key}('", idx)
|
||||
if idx == -1:
|
||||
break
|
||||
flag = True
|
||||
if not (idx == 0 or content[idx - 1] in [".", ",", " ", ">"]): # maybe like str etc.
|
||||
idx += 1
|
||||
continue
|
||||
idx += len(key) + 2
|
||||
if flag:
|
||||
end = content.find("')", idx)
|
||||
else:
|
||||
end = content.find('")', idx)
|
||||
if end == -1:
|
||||
break
|
||||
strs.append(content[idx:end])
|
||||
idx = end
|
||||
return strs
|
||||
|
||||
def main(search_dir, keywords, exts, out, recursive, locales):
|
||||
print("seach dir: ", dir)
|
||||
print("keywords: ", keywords)
|
||||
print("extentions:", exts)
|
||||
print("out dir: ", out)
|
||||
print("")
|
||||
files = get_files(search_dir, recursive)
|
||||
print(f"{len(files)} files found")
|
||||
keys_list = []
|
||||
for f in files:
|
||||
strs = get_i18n_strs(f, keywords)
|
||||
keys_list.extend(strs)
|
||||
keys_list = list(set(keys_list))
|
||||
keys_list.sort()
|
||||
keys_dict = {}
|
||||
for i in keys_list:
|
||||
keys_dict[i] = i
|
||||
os.makedirs(out, exist_ok=True)
|
||||
for locale in locales:
|
||||
print(f"gen locale [ {locale} ]")
|
||||
path = os.path.join(out, f"{locale}.yaml")
|
||||
old = {}
|
||||
if os.path.exists(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
old = yaml.safe_load(f)
|
||||
if old is None:
|
||||
old = {}
|
||||
# update by old
|
||||
for k in keys_dict:
|
||||
if k in old:
|
||||
keys_dict[k] = old[k]
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(keys_dict, f, allow_unicode=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
paser = argparse.ArgumentParser()
|
||||
paser.add_argument("-k", "--keywords", nargs="+", type=str, default=["_", "tr"], help="translate function keywords to search")
|
||||
paser.add_argument("-r", action="store_true", help="recursive search dir")
|
||||
paser.add_argument("-e", "--exts", nargs="+", type=str, default=[".c", ".cpp", ".h", ".hpp", ".py"], help="file extention to search")
|
||||
paser.add_argument("-l", "--locales", nargs="+", type=str, default=["en", "zh"], help="locals of region, like en zh ja etc.")
|
||||
paser.add_argument("-o", "--out", type=str, default="locales", help="translation files output directory")
|
||||
paser.add_argument("-d", "--dir", type=str, help="where to search", required=True)
|
||||
args = paser.parse_args()
|
||||
main(args.dir, args.keywords, args.exts, args.out, args.r, args.locales)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from .version import __version__
|
||||
import os
|
||||
from .app_deploy import serve
|
||||
from .app_release import pack
|
||||
from .i18n import main as i18n_main
|
||||
|
||||
def deploy(port, pkg_path):
|
||||
if not pkg_path:
|
||||
@@ -15,15 +16,29 @@ def deploy(port, pkg_path):
|
||||
serve(pkg_path, port)
|
||||
|
||||
def main():
|
||||
cmds = ["release", "deploy"]
|
||||
parser = argparse.ArgumentParser(description='maixtool, tools for maix series development')
|
||||
parser.add_argument('-v', '--version', action='version', version=__version__)
|
||||
parser.add_argument('-p', "--port", default=8888, type=int, help="deploy server port")
|
||||
parser.add_argument("--pkg", default="", type=str, help="app package path")
|
||||
parser.add_argument("command", help="command to run", choices=cmds)
|
||||
args = parser.parse_args()
|
||||
if args.command == "release":
|
||||
pack(os.getcwd())
|
||||
elif args.command == "deploy":
|
||||
deploy(args.port, args.pkg)
|
||||
subparsers = parser.add_subparsers(help='command help', dest="cmd")
|
||||
# release
|
||||
parser_release = subparsers.add_parser("release", help="release APP")
|
||||
# deploy
|
||||
parser_deploy = subparsers.add_parser("deploy", help="release APP")
|
||||
parser_deploy.add_argument('-p', "--port", default=8888, type=int, help="deploy server port")
|
||||
parser_deploy.add_argument("--pkg", default="", type=str, help="app package path")
|
||||
# i18n
|
||||
parser_i18n = subparsers.add_parser("i18n", help="release APP")
|
||||
parser_i18n.add_argument("-k", "--keywords", nargs="+", type=str, default=["_", "tr"], help="translate function keywords to search")
|
||||
parser_i18n.add_argument("-r", action="store_true", help="recursive search dir")
|
||||
parser_i18n.add_argument("-e", "--exts", nargs="+", type=str, default=[".c", ".cpp", ".h", ".hpp", ".py"], help="file extention to search")
|
||||
parser_i18n.add_argument("-l", "--locales", nargs="+", type=str, default=["en", "zh"], help="locals of region, like en zh ja etc.")
|
||||
parser_i18n.add_argument("-o", "--out", type=str, default="locales", help="translation files output directory")
|
||||
parser_i18n.add_argument("-d", "--dir", type=str, help="where to search", required=True)
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.cmd == "release":
|
||||
pack(os.getcwd())
|
||||
elif args.cmd == "deploy":
|
||||
deploy(args.port, args.pkg)
|
||||
elif args.cmd == "i18n":
|
||||
i18n_main(args.dir, args.keywords, args.exts, args.out, args.r, args.locales)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
__version__ = "1.3.3"
|
||||
__version__ = "1.3.4"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user