1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "js_relative_time_format_addon.h"
16
17 #include <cmath>
18 #include "error_util.h"
19 #include "i18n_hilog.h"
20 #include "i18n_types.h"
21 #include "js_utils.h"
22 #include "locale_helper.h"
23 #include "utils.h"
24
25 namespace OHOS {
26 namespace Global {
27 namespace I18n {
28
29 std::unordered_map<std::string, std::vector<std::string>> JSRelativeTimeFormatAddon::optionAvaliableMap {
30 {"localeMatcher", {"lookup", "best fit"}},
31 {"numeric", {"always", "auto"}},
32 {"style", {"long", "short", "narrow"}},
33 {"unit", {"year", "years", "quarter", "quarters", "month", "months", "week", "weeks",
34 "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds"}},
35 };
36
JSRelativeTimeFormatAddon()37 JSRelativeTimeFormatAddon::JSRelativeTimeFormatAddon()
38 {
39 }
40
~JSRelativeTimeFormatAddon()41 JSRelativeTimeFormatAddon::~JSRelativeTimeFormatAddon()
42 {
43 }
44
Destructor(napi_env env,void * nativeObject,void * hint)45 void JSRelativeTimeFormatAddon::Destructor(napi_env env, void *nativeObject, void *hint)
46 {
47 if (!nativeObject) {
48 return;
49 }
50 delete reinterpret_cast<JSRelativeTimeFormatAddon *>(nativeObject);
51 nativeObject = nullptr;
52 }
53
InitJsRelativeTimeFormat(napi_env env,napi_value exports)54 napi_value JSRelativeTimeFormatAddon::InitJsRelativeTimeFormat(napi_env env, napi_value exports)
55 {
56 napi_property_descriptor properties[] = {
57 DECLARE_NAPI_FUNCTION("format", Format),
58 DECLARE_NAPI_FUNCTION("formatToParts", FormatToParts),
59 DECLARE_NAPI_FUNCTION("resolvedOptions", GetNumberResolvedOptions),
60 DECLARE_NAPI_STATIC_FUNCTION("supportedLocalesOf", SupportedLocalesOf),
61 };
62 napi_value constructor = nullptr;
63 napi_status status = napi_define_class(env, "RelativeTimeFormat", NAPI_AUTO_LENGTH,
64 RelativeTimeFormatConstructor, nullptr,
65 sizeof(properties) / sizeof(napi_property_descriptor), properties, &constructor);
66 if (status != napi_ok) {
67 HILOG_ERROR_I18N("InitJsRelativeTimeFormat: Define class failed when init JsRelativeTimeFormat");
68 return nullptr;
69 }
70 status = napi_set_named_property(env, exports, "RelativeTimeFormat", constructor);
71 if (status != napi_ok) {
72 HILOG_ERROR_I18N("InitJsRelativeTimeFormat: Set property failed when init JsRelativeTimeFormat");
73 return nullptr;
74 }
75 return exports;
76 }
77
RelativeTimeFormatConstructor(napi_env env,napi_callback_info info)78 napi_value JSRelativeTimeFormatAddon::RelativeTimeFormatConstructor(napi_env env, napi_callback_info info)
79 {
80 size_t argc = 2;
81 napi_value argv[2] = { nullptr };
82 napi_value thisVar = nullptr;
83 void *data = nullptr;
84 napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
85 if (status != napi_ok) {
86 HILOG_ERROR_I18N("RelativeTimeFormatConstructor: Get parameter info failed");
87 return nullptr;
88 }
89 napi_value newTarget;
90 status = napi_get_new_target(env, info, &newTarget);
91 if (status != napi_ok || newTarget == nullptr) {
92 HILOG_ERROR_I18N("RelativeTimeFormatConstructor: Intl.RelativeTimeFormat requires new");
93 napi_throw_type_error(env, nullptr, "newTarget is undefined");
94 return nullptr;
95 }
96 JSRelativeTimeFormatAddon* obj = new (std::nothrow) JSRelativeTimeFormatAddon();
97 if (obj == nullptr) {
98 HILOG_ERROR_I18N("RelativeTimeFormatConstructor: Create JSRelativeTimeFormatAddon failed");
99 return nullptr;
100 }
101 std::vector<std::string> localeTags;
102 if (!obj->GetParameterLocales(env, localeTags, argc, argv)) {
103 delete obj;
104 return nullptr;
105 }
106 std::map<std::string, std::string> map = {};
107 if (argc > 1) {
108 GetRelativeTimeOptionValues(env, argv[1], map);
109 }
110 status = napi_wrap(env, thisVar, reinterpret_cast<void *>(obj), JSRelativeTimeFormatAddon::Destructor,
111 nullptr, nullptr);
112 if (status != napi_ok) {
113 delete obj;
114 HILOG_ERROR_I18N("RelativeTimeFormatConstructor: Wrap JSRelativeTimeFormatAddon failed");
115 return nullptr;
116 }
117 if (!obj->InitRelativeTimeFormatContext(env, info, localeTags, map)) {
118 HILOG_ERROR_I18N("Init RelativeTimeFormat failed");
119 return nullptr;
120 }
121 return thisVar;
122 }
123
GetParameterLocales(napi_env env,std::vector<std::string> & localeTags,const size_t & argc,napi_value * argv)124 bool JSRelativeTimeFormatAddon::GetParameterLocales(napi_env env,
125 std::vector<std::string> &localeTags, const size_t &argc, napi_value* argv)
126 {
127 if (argc < 1) {
128 localeTags.push_back("en-US");
129 return true;
130 }
131 int32_t code = 0;
132 std::vector<std::string> localeArray = JSUtils::GetLocaleArray(env, argv[0], code);
133 if (localeArray.empty()) {
134 localeTags.push_back("en-US");
135 return true;
136 }
137 std::string checkResult = LocaleHelper::CheckParamLocales(localeArray);
138 if (!checkResult.empty()) {
139 ErrorUtil::NapiThrowUndefined(env, checkResult);
140 return false;
141 }
142 ErrorMessage errorMsg;
143 localeArray = RelativeTimeFormat::CanonicalizeLocales(localeArray, errorMsg);
144 if (errorMsg.type != ErrorType::NO_ERROR) {
145 napi_throw_range_error(env, nullptr, errorMsg.message.c_str());
146 return false;
147 }
148 localeTags.assign(localeArray.begin(), localeArray.end());
149 return true;
150 }
151
GetRelativeTimeOptionValues(napi_env env,napi_value options,std::map<std::string,std::string> & map)152 void JSRelativeTimeFormatAddon::GetRelativeTimeOptionValues(napi_env env,
153 napi_value options, std::map<std::string, std::string> &map)
154 {
155 JSUtils::GetOptionValue(env, options, "localeMatcher", map);
156 CheckOptionValue(env, map, "localeMatcher");
157 JSUtils::GetOptionValue(env, options, "numeric", map);
158 CheckOptionValue(env, map, "numeric");
159 JSUtils::GetOptionValue(env, options, "style", map);
160 CheckOptionValue(env, map, "style");
161 }
162
CheckOptionValue(napi_env env,std::map<std::string,std::string> & map,const std::string & optionName)163 void JSRelativeTimeFormatAddon::CheckOptionValue(napi_env env, std::map<std::string, std::string> &map,
164 const std::string &optionName)
165 {
166 if (optionAvaliableMap.find(optionName) == optionAvaliableMap.end()) {
167 return;
168 }
169 if (map.find(optionName) != map.end()) {
170 std::string optionValue = map[optionName];
171 CheckOptionValue(env, optionValue, optionName);
172 }
173 }
174
CheckOptionValue(napi_env env,const std::string & optionValue,const std::string & optionName)175 void JSRelativeTimeFormatAddon::CheckOptionValue(napi_env env, const std::string &optionValue,
176 const std::string &optionName)
177 {
178 if (optionAvaliableMap.find(optionName) == optionAvaliableMap.end()) {
179 return;
180 }
181 std::vector<std::string> aviliableValues = optionAvaliableMap[optionName];
182 auto it = std::find(aviliableValues.begin(), aviliableValues.end(), optionValue);
183 if (it != aviliableValues.end()) {
184 return;
185 }
186 if (!ThrowRangeError(env)) {
187 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::CheckOptionValue:"
188 "throw error for %{public}s failed", optionName.c_str());
189 return;
190 }
191 }
192
ThrowRangeError(napi_env env)193 bool JSRelativeTimeFormatAddon::ThrowRangeError(napi_env env)
194 {
195 const char* msg = "Value out of range for locale options property";
196 napi_status status = napi_throw_range_error(env, nullptr, msg);
197 if (status != napi_ok) {
198 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::ThrowRangeError: throw range error failed");
199 return false;
200 }
201 return true;
202 }
203
InitRelativeTimeFormatContext(napi_env env,napi_callback_info info,std::vector<std::string> localeTags,std::map<std::string,std::string> & map)204 bool JSRelativeTimeFormatAddon::InitRelativeTimeFormatContext(napi_env env, napi_callback_info info,
205 std::vector<std::string> localeTags, std::map<std::string, std::string> &map)
206 {
207 relativetimefmt_ = std::make_shared<RelativeTimeFormat>(localeTags, map, true);
208 return relativetimefmt_ != nullptr;
209 }
210
Format(napi_env env,napi_callback_info info)211 napi_value JSRelativeTimeFormatAddon::Format(napi_env env, napi_callback_info info)
212 {
213 size_t argc = 2;
214 napi_value argv[2] = { nullptr };
215 napi_value thisVar = nullptr;
216 napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr);
217 if (status != napi_ok) {
218 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::Format: Get parameter info failed");
219 return JSUtils::CreateEmptyString(env);
220 }
221 if (argc < 2) { // 2 is required parameter count
222 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::Format: parameter less then required");
223 return JSUtils::CreateEmptyString(env);
224 }
225 napi_valuetype valueType = napi_valuetype::napi_undefined;
226 status = napi_typeof(env, argv[0], &valueType);
227 if (status != napi_ok) {
228 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::Format: Get param type failed.");
229 return JSUtils::CreateEmptyString(env);
230 }
231 int32_t code = 0;
232 double number = JSUtils::GetDoubleFromNapiValue(env, argv[0], valueType, code);
233 if (code != 0) {
234 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::Format: Get number failed");
235 return JSUtils::CreateEmptyString(env);
236 }
237 std::string unit = JSUtils::GetString(env, argv[1], code);
238 if (code != 0) {
239 return JSUtils::CreateEmptyString(env);
240 }
241 CheckOptionValue(env, unit, "unit");
242 JSRelativeTimeFormatAddon *obj = nullptr;
243 status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
244 if (status != napi_ok || !obj || !obj->relativetimefmt_) {
245 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::Format: Get RelativeTimeFormat object failed");
246 return JSUtils::CreateEmptyString(env);
247 }
248 std::string value = obj->relativetimefmt_->Format(number, unit);
249 return JSUtils::CreateString(env, value);
250 }
251
GetNumberResolvedOptions(napi_env env,napi_callback_info info)252 napi_value JSRelativeTimeFormatAddon::GetNumberResolvedOptions(napi_env env, napi_callback_info info)
253 {
254 napi_value thisVar = nullptr;
255 void *data = nullptr;
256 napi_status status = napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, &data);
257 if (status != napi_ok) {
258 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::GetNumberResolvedOptions: Get parameter info failed");
259 return JSUtils::CreateEmptyObject(env);
260 }
261 JSRelativeTimeFormatAddon *obj = nullptr;
262 status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
263 if (status != napi_ok || !obj || !obj->relativetimefmt_) {
264 HILOG_ERROR_I18N("GetNumberResolvedOptions: Get RelativeTimeFormat object failed");
265 return JSUtils::CreateEmptyObject(env);
266 }
267 napi_value result = nullptr;
268 status = napi_create_object(env, &result);
269 if (status != napi_ok) {
270 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::GetNumberResolvedOptions: napi_create_object failed");
271 return JSUtils::CreateEmptyObject(env);
272 }
273 std::map<std::string, std::string> options = {};
274 obj->relativetimefmt_->GetResolvedOptions(options);
275 JSUtils::SetOptionProperties(env, result, options, "locale");
276 JSUtils::SetOptionProperties(env, result, options, "style");
277 JSUtils::SetOptionProperties(env, result, options, "numeric");
278 JSUtils::SetOptionProperties(env, result, options, "numberingSystem");
279 return result;
280 }
281
FormatToParts(napi_env env,napi_callback_info info)282 napi_value JSRelativeTimeFormatAddon::FormatToParts(napi_env env, napi_callback_info info)
283 {
284 size_t argc = 2;
285 const size_t argsCount = 2; // 2 is required parameter count
286 napi_value argv[argsCount] = { nullptr };
287 napi_value thisVar = nullptr;
288 napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr);
289 if (status != napi_ok) {
290 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::FormatToParts: Get parameter info failed");
291 return JSUtils::CreateEmptyArray(env);
292 }
293 if (argc < argsCount) {
294 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::FormatToParts: parameter count less then required.");
295 return JSUtils::CreateEmptyArray(env);
296 }
297 napi_valuetype valueType = napi_valuetype::napi_undefined;
298 status = napi_typeof(env, argv[0], &valueType);
299 if (status != napi_ok) {
300 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::FormatToParts: Get param type failed.");
301 return JSUtils::CreateEmptyArray(env);
302 }
303 int32_t code = 0;
304 double number = JSUtils::GetDoubleFromNapiValue(env, argv[0], valueType, code);
305 if (code != 0) {
306 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::FormatToParts: Get parameter number failed");
307 return JSUtils::CreateEmptyArray(env);
308 }
309 std::string unit = JSUtils::GetString(env, argv[argsCount - 1], code);
310 if (code != 0) {
311 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::FormatToParts: Get second parameter failed");
312 return JSUtils::CreateEmptyArray(env);
313 }
314 return GetFormatToPartsInner(env, number, unit, thisVar);
315 }
316
GetFormatToPartsInner(napi_env env,double number,const std::string & unit,napi_value & thisVar)317 napi_value JSRelativeTimeFormatAddon::GetFormatToPartsInner(napi_env env, double number,
318 const std::string &unit, napi_value &thisVar)
319 {
320 JSRelativeTimeFormatAddon *obj = nullptr;
321 napi_status status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
322 if (status != napi_ok || !obj || !obj->relativetimefmt_) {
323 HILOG_ERROR_I18N("GetFormatToPartsInner: Get NumberFormat object failed");
324 return JSUtils::CreateEmptyArray(env);
325 }
326 std::vector<std::vector<std::string>> relativeTimeParts;
327 obj->relativetimefmt_->FormatToParts(number, unit, relativeTimeParts);
328 return SetNumberFormatParts(env, relativeTimeParts);
329 }
330
SupportedLocalesOf(napi_env env,napi_callback_info info)331 napi_value JSRelativeTimeFormatAddon::SupportedLocalesOf(napi_env env, napi_callback_info info)
332 {
333 size_t argc = 2;
334 napi_value argv[2] = { nullptr };
335 napi_value thisVar = nullptr;
336 napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr);
337 if (status != napi_ok) {
338 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::SupportedLocalesOf: Get parameter info failed");
339 return JSUtils::CreateEmptyArray(env);
340 }
341 std::vector<std::string> localeTags;
342 if (argc < 1) {
343 return JSUtils::CreateArray(env, localeTags);
344 }
345 int32_t code = 0;
346 std::vector<std::string> localeArray = JSUtils::GetLocaleArray(env, argv[0], code);
347 localeTags.assign(localeArray.begin(), localeArray.end());
348 std::map<std::string, std::string> map = {};
349 if (argc > 1) {
350 JSUtils::GetOptionValue(env, argv[1], "localeMatcher", map);
351 CheckOptionValue(env, map, "localeMatcher");
352 }
353 I18nErrorCode i18nStatus = I18nErrorCode::SUCCESS;
354 std::vector<std::string> resultLocales = RelativeTimeFormat::SupportedLocalesOf(localeTags, map, i18nStatus);
355 if (i18nStatus == I18nErrorCode::INVALID_LOCALE_TAG) {
356 HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::SupportedLocalesOf: SupportedLocalesOf status fail");
357 ErrorUtil::NapiThrowUndefined(env, "invalid locale");
358 return nullptr;
359 } else if (i18nStatus != I18nErrorCode::SUCCESS) {
360 ErrorUtil::NapiThrowUndefined(env, "getStringOption failed");
361 return nullptr;
362 }
363 return JSUtils::CreateArray(env, resultLocales);
364 }
365
SetNumberFormatParts(napi_env env,const std::vector<std::vector<std::string>> & numberParts)366 napi_value JSRelativeTimeFormatAddon::SetNumberFormatParts(napi_env env,
367 const std::vector<std::vector<std::string>> &numberParts)
368 {
369 napi_value result = nullptr;
370 napi_status status = napi_create_array_with_length(env, numberParts.size(), &result);
371 if (status != napi_ok) {
372 HILOG_ERROR_I18N("SetNumberFormatParts: Failed to create array");
373 return JSUtils::CreateEmptyArray(env);
374 }
375 for (size_t i = 0; i < numberParts.size(); i++) {
376 if (numberParts[i].size() < 2) { // 2 is minimum vector's size
377 HILOG_ERROR_I18N("SetNumberFormatParts: numberParts's item in %{public}zu lack key&value pair", i);
378 return JSUtils::CreateEmptyArray(env);
379 }
380 napi_value value = JSUtils::CreateString(env, numberParts[i][1].c_str());
381 if (value == nullptr) {
382 HILOG_ERROR_I18N("Failed to create string item numberParts[i][1].");
383 return JSUtils::CreateEmptyArray(env);
384 }
385 napi_value type = JSUtils::CreateString(env, numberParts[i][0].c_str());
386 if (type == nullptr) {
387 HILOG_ERROR_I18N("Failed to create string item numberParts[i][0].");
388 return JSUtils::CreateEmptyArray(env);
389 }
390 napi_value unit = nullptr;
391 if (numberParts[i].size() > 2) { // 2 is vector's size which not contains unit
392 unit = JSUtils::CreateString(env, numberParts[i][2].c_str());
393 if (unit == nullptr) {
394 HILOG_ERROR_I18N("Failed to create string item numberParts[i][2].");
395 return JSUtils::CreateEmptyArray(env);
396 }
397 }
398 std::unordered_map<std::string, napi_value> propertys {
399 { "type", type },
400 { "value", value },
401 { "unit", unit }
402 };
403 std::vector<std::string> keyVect = { "type", "value", "unit" };
404 int32_t code = 0;
405 napi_value formatInfo = JSUtils::CreateArrayItem(env, propertys, code, keyVect);
406 if (code != 0) {
407 return JSUtils::CreateEmptyArray(env);
408 }
409 status = napi_set_element(env, result, i, formatInfo);
410 if (status != napi_ok) {
411 HILOG_ERROR_I18N("Failed to set array item");
412 return JSUtils::CreateEmptyArray(env);
413 }
414 }
415 return result;
416 }
417
418 } // namespace I18n
419 } // namespace Global
420 } // namespace OHOS