* Copyright (c) 2022-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "plugins/i18n/android/java/jni/i18n_plugin_jni.h"
#include <codecvt>
#include <jni.h>
#include <locale>
#include <map>
#include <string>
#include "inner_api/plugin_utils_inner.h"
#include "log.h"
#include "plugin_utils.h"
namespace OHOS::Plugin {
namespace {
const char I18N_PLUGIN_CLASS_NAME[] = "ohos/ace/plugin/i18nplugin/I18NPlugin";
static const JNINativeMethod METHODS[] = {
{ "nativeInit", "()V", reinterpret_cast<void*>(I18NPluginJni::NativeInit) },
};
struct {
jmethodID is24HourClock;
jmethodID getSystemLocale;
jmethodID getSystemLanguage;
jmethodID getSystemRegion;
jmethodID getSystemTimezone;
jmethodID getAppPreferredLanguage;
jmethodID setAppPreferredLanguage;
jmethodID getSystemCountries;
jmethodID getSystemLanguages;
jmethodID getPreferredLanguages;
jmethodID getFirstPreferredLanguage;
jmethodID getUsingLocalDigit;
jmethodID isSuggested;
jmethodID getAvailableIDs;
jobject globalRef;
} g_pluginClass;
}
bool I18NPluginJni::Register(void* env)
{
auto* jniEnv = static_cast<JNIEnv*>(env);
CHECK_NULL_RETURN(jniEnv, false);
jclass cls = jniEnv->FindClass(I18N_PLUGIN_CLASS_NAME);
CHECK_NULL_RETURN(cls, false);
bool ret = jniEnv->RegisterNatives(cls, METHODS, sizeof(METHODS) / sizeof(METHODS[0])) == 0;
jniEnv->DeleteLocalRef(cls);
if (!ret) {
LOGE("I18NPlugin JNI: RegisterNatives fail.");
return false;
}
return true;
}
void I18NPluginJni::NativeInit(JNIEnv* env, jobject jobj)
{
CHECK_NULL_VOID(env);
g_pluginClass.globalRef = env->NewGlobalRef(jobj);
CHECK_NULL_VOID(g_pluginClass.globalRef);
jclass cls = env->GetObjectClass(jobj);
CHECK_NULL_VOID(cls);
static std::map<const char*, std::pair<jmethodID&, const char*>> jniFuncMap = {
{"is24HourClock", {g_pluginClass.is24HourClock, "()Z"}},
{"getSystemLocale", {g_pluginClass.getSystemLocale, "()Ljava/lang/String;"}},
{"getSystemLanguage", {g_pluginClass.getSystemLanguage, "()Ljava/lang/String;"}},
{"getSystemRegion", {g_pluginClass.getSystemRegion, "()Ljava/lang/String;"}},
{"getSystemTimezone", {g_pluginClass.getSystemTimezone, "()Ljava/lang/String;"}},
{"getAppPreferredLanguage", {g_pluginClass.getAppPreferredLanguage, "()Ljava/lang/String;"}},
{"setAppPreferredLanguage", {g_pluginClass.setAppPreferredLanguage, "(Ljava/lang/String;)V"}},
{"getSystemLanguages", {g_pluginClass.getSystemLanguages, "()[Ljava/lang/String;"}},
{"getSystemCountries", {g_pluginClass.getSystemCountries, "(Ljava/lang/String;)[Ljava/lang/String;"}},
{"getPreferredLanguageList", {g_pluginClass.getPreferredLanguages, "()[Ljava/lang/String;"}},
{"getFirstPreferredLanguage", {g_pluginClass.getFirstPreferredLanguage, "()Ljava/lang/String;"}},
{"getUsingLocalDigit", {g_pluginClass.getUsingLocalDigit, "()Z"}},
{"isSuggested", {g_pluginClass.isSuggested, "(Ljava/lang/String;Ljava/lang/String;)Z"}},
{"getAvailableIDs", {g_pluginClass.getAvailableIDs, "()[Ljava/lang/String;"}},
};
for (auto& jniInterface : jniFuncMap) {
jniInterface.second.first = env->GetMethodID(cls, jniInterface.first, jniInterface.second.second);
CHECK_NULL_VOID(jniInterface.second.first);
}
env->DeleteLocalRef(cls);
}
bool I18NPluginJni::Is24HourClock()
{
bool result = true;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.is24HourClock)) {
LOGE("I18NPluginJni get none ptr error");
return result;
}
result = static_cast<bool>(env->CallBooleanMethod(g_pluginClass.globalRef, g_pluginClass.is24HourClock));
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call is24HourClock failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
return result;
}
std::string I18NPluginJni::GetSystemLocale()
{
jstring result;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.getSystemLocale)) {
LOGE("I18NPluginJni get none ptr error");
return "";
}
result = static_cast<jstring>(env->CallObjectMethod(g_pluginClass.globalRef, g_pluginClass.getSystemLocale));
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call getSystemLocale failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
std::string locale = env->GetStringUTFChars(result, NULL);
if (result != nullptr) {
env->DeleteLocalRef(result);
}
return locale;
}
std::string I18NPluginJni::GetSystemLanguage()
{
jstring result;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.getSystemLanguage)) {
LOGE("I18NPluginJni get none ptr error");
return "";
}
result = static_cast<jstring>(env->CallObjectMethod(g_pluginClass.globalRef, g_pluginClass.getSystemLanguage));
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call getSystemLanguage failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
std::string language = env->GetStringUTFChars(result, NULL);
if (result != nullptr) {
env->DeleteLocalRef(result);
}
return language;
}
std::string I18NPluginJni::GetSystemRegion()
{
jstring result;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.getSystemRegion)) {
LOGE("I18NPluginJni get none ptr error");
return "";
}
result = static_cast<jstring>(env->CallObjectMethod(g_pluginClass.globalRef, g_pluginClass.getSystemRegion));
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call getSystemRegion failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
std::string region = env->GetStringUTFChars(result, NULL);
if (result != nullptr) {
env->DeleteLocalRef(result);
}
return region;
}
std::string I18NPluginJni::GetSystemTimezone()
{
jstring result;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.getSystemTimezone)) {
LOGE("I18NPluginJni get none ptr error");
return "";
}
result = static_cast<jstring>(env->CallObjectMethod(g_pluginClass.globalRef, g_pluginClass.getSystemTimezone));
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call getSystemTimezone failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
std::string timezone = env->GetStringUTFChars(result, NULL);
if (result != nullptr) {
env->DeleteLocalRef(result);
}
return timezone;
}
std::string I18NPluginJni::GetAppPreferredLanguage()
{
jstring result;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.getAppPreferredLanguage)) {
LOGE("getAppPreferredLanguage: I18NPluginJni get none ptr error");
return "";
}
result = static_cast<jstring>(
env->CallObjectMethod(g_pluginClass.globalRef, g_pluginClass.getAppPreferredLanguage));
if (env->ExceptionCheck()) {
LOGE("getAppPreferredLanguage: I18N JNI: call getAppPreferredLanguage failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
std::string appPreferredLanguage = env->GetStringUTFChars(result, NULL);
if (result != nullptr) {
env->DeleteLocalRef(result);
}
return appPreferredLanguage;
}
void I18NPluginJni::SetAppPreferredLanguage(const std::string& languageTag)
{
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.setAppPreferredLanguage)) {
LOGE("SetAppPreferredLanguage: I18NPluginJni get none ptr error");
return;
}
jstring jLanguageTag = env->NewStringUTF(languageTag.c_str());
env->CallVoidMethod(g_pluginClass.globalRef, g_pluginClass.setAppPreferredLanguage, jLanguageTag);
env->DeleteLocalRef(jLanguageTag);
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call setAppPreferredLanguage failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
}
std::unordered_set<std::string> I18NPluginJni::GetSystemLanguages()
{
std::unordered_set<std::string> result;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.getSystemLanguages)) {
LOGE("I18NPluginJni get none ptr error");
return result;
}
jobjectArray sysLanguages = static_cast<jobjectArray>(
env->CallObjectMethod(g_pluginClass.globalRef, g_pluginClass.getSystemLanguages));
jsize arrayLength = env->GetArrayLength(sysLanguages);
jstring jstr = nullptr;
const char* str = nullptr;
for (int32_t index = 0; index < arrayLength; index++) {
jstr = static_cast<jstring>(env->GetObjectArrayElement(sysLanguages, index));
str = env->GetStringUTFChars(jstr, NULL);
if (str != nullptr) {
result.insert(std::string(str));
env->ReleaseStringUTFChars(jstr, str);
}
if (jstr != nullptr) {
env->DeleteLocalRef(jstr);
}
str = nullptr;
}
env->DeleteLocalRef(sysLanguages);
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call getSystemLanguages failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
return result;
}
jstring I18NPluginJni::StringToJavaString(JNIEnv* env, const std::string& str)
{
if (env == nullptr) {
return nullptr;
}
if (str.empty()) {
return env->NewStringUTF("");
}
std::u16string u16str = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> {}.from_bytes(str);
return env->NewString(reinterpret_cast<const jchar*>(u16str.data()), u16str.length());
}
std::unordered_set<std::string> I18NPluginJni::GetSystemCountries(const std::string& language)
{
std::unordered_set<std::string> result;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.getSystemCountries)) {
LOGE("I18NPluginJni get none ptr error");
return result;
}
jstring jLanguage = StringToJavaString(env, language);
jobjectArray countryCodes = static_cast<jobjectArray>(
env->CallObjectMethod(g_pluginClass.globalRef, g_pluginClass.getSystemCountries, jLanguage));
jsize arrayLength = env->GetArrayLength(countryCodes);
jstring jstr = nullptr;
const char* str = nullptr;
for (int32_t index = 0; index < arrayLength; index++) {
jstr = static_cast<jstring>(env->GetObjectArrayElement(countryCodes, index));
str = env->GetStringUTFChars(jstr, NULL);
if (str != nullptr) {
result.insert(std::string(str));
env->ReleaseStringUTFChars(jstr, str);
}
if (jstr != nullptr) {
env->DeleteLocalRef(jstr);
}
str = nullptr;
}
env->DeleteLocalRef(jLanguage);
env->DeleteLocalRef(countryCodes);
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call getSystemCountries failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
return result;
}
std::unordered_set<std::string> I18NPluginJni::GetAvailableIDs()
{
std::unordered_set<std::string> result;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.getAvailableIDs)) {
LOGE("I18NPluginJni get none ptr error");
return result;
}
jobjectArray availableIds = static_cast<jobjectArray>(
env->CallObjectMethod(g_pluginClass.globalRef, g_pluginClass.getAvailableIDs));
jsize arrayLength = env->GetArrayLength(availableIds);
jstring jstr = nullptr;
const char* str = nullptr;
for (int32_t index = 0; index < arrayLength; index++) {
jstr = static_cast<jstring>(env->GetObjectArrayElement(availableIds, index));
str = env->GetStringUTFChars(jstr, NULL);
if (str != nullptr) {
result.insert(std::string(str));
env->ReleaseStringUTFChars(jstr, str);
}
if (jstr != nullptr) {
env->DeleteLocalRef(jstr);
}
str = nullptr;
}
env->DeleteLocalRef(availableIds);
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call getAvailableIDs failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
return result;
}
std::vector<std::string> I18NPluginJni::GetPreferredLanguages()
{
std::vector<std::string> result;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.getPreferredLanguages)) {
LOGE("I18NPluginJni get none ptr error");
return result;
}
jobjectArray sysLanguages = static_cast<jobjectArray>(
env->CallObjectMethod(g_pluginClass.globalRef, g_pluginClass.getPreferredLanguages));
jsize arrayLength = env->GetArrayLength(sysLanguages);
jstring jstr = nullptr;
const char* str = nullptr;
for (int32_t index = 0; index < arrayLength; index++) {
jstr = static_cast<jstring>(env->GetObjectArrayElement(sysLanguages, index));
str = env->GetStringUTFChars(jstr, NULL);
if (str != nullptr) {
result.push_back(std::string(str));
env->ReleaseStringUTFChars(jstr, str);
}
if (jstr != nullptr) {
env->DeleteLocalRef(jstr);
}
str = nullptr;
}
env->DeleteLocalRef(sysLanguages);
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call GetPreferredLanguages failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
return result;
}
std::string I18NPluginJni::GetFirstPreferredLanguage()
{
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.getFirstPreferredLanguage)) {
LOGE("I18NPluginJni get none ptr error");
return std::string {""};
}
jstring language = static_cast<jstring>(
env->CallObjectMethod(g_pluginClass.globalRef, g_pluginClass.getFirstPreferredLanguage));
std::string locale = env->GetStringUTFChars(language, NULL);
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call getFirstPreferredLanguage failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
if (language != nullptr) {
env->DeleteLocalRef(language);
}
return locale;
}
bool I18NPluginJni::GetUsingLocalDigit()
{
bool result = false;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.getUsingLocalDigit)) {
LOGE("I18NPluginJni get none ptr error");
return result;
}
result = static_cast<bool>(env->CallBooleanMethod(g_pluginClass.globalRef, g_pluginClass.getUsingLocalDigit));
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call getUsingLocalDigit failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
return result;
}
bool I18NPluginJni::IsSuggested(const std::string& language, const std::string& region)
{
bool result = false;
auto env = ARKUI_X_Plugin_GetJniEnv();
if (!(env) || !(g_pluginClass.globalRef) || !(g_pluginClass.isSuggested)) {
LOGE("I18NPluginJni get none ptr error");
return result;
}
jstring jLanguage = StringToJavaString(env, language);
jstring jRegion = StringToJavaString(env, region);
result = static_cast<bool>(env->CallBooleanMethod(
g_pluginClass.globalRef, g_pluginClass.isSuggested, jLanguage, jRegion));
env->DeleteLocalRef(jLanguage);
env->DeleteLocalRef(jRegion);
if (env->ExceptionCheck()) {
LOGE("I18N JNI: call isSuggested failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
return result;
}
}