Utils.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. import {
  2. Prefab,
  3. resources,
  4. Node,
  5. find,
  6. instantiate,
  7. Vec3,
  8. log,
  9. Component,
  10. Button,
  11. warn,
  12. Asset,
  13. error,
  14. v2,
  15. UITransform,
  16. tween,
  17. v3,
  18. Vec2,
  19. director,
  20. ImageAsset,
  21. assetManager,
  22. } from "cc";
  23. import BigNumber from "./BigNumber";
  24. import { EDITOR } from "cc/env";
  25. import { LanguageManager } from "./LanguageManager";
  26. import PoolMgr from "./PoolMgr";
  27. import BaseUI from "../base/BaseUI";
  28. import { Tips } from "../mgr/Tips";
  29. export enum EFLY_TYPE {
  30. 金币,
  31. 体力,
  32. 钻石,
  33. }
  34. export default class Utils {
  35. public static copyText(text: string) {
  36. navigator.clipboard.writeText(text).then(
  37. (res) => {
  38. console.log(" writeText success: ", res);
  39. // Tips.show(Utils.setI18nLabel("Share.LinkCopied"));
  40. },
  41. (err) => {
  42. console.log(" writeText error: ", err);
  43. this.copyTextFallback(text);
  44. }
  45. );
  46. }
  47. private static copyTextFallback(text) {
  48. const textArea = document.createElement("textarea");
  49. textArea.value = text;
  50. document.body.appendChild(textArea);
  51. textArea.select();
  52. try {
  53. document.execCommand("copy");
  54. Tips.show(Utils.setI18nLabel("Share.LinkCopied")); //"Link copied!");
  55. } catch (err) {
  56. Tips.show(Utils.setI18nLabel("Share.Title1"));
  57. }
  58. document.body.removeChild(textArea);
  59. }
  60. static sleep(ms: number) {
  61. return new Promise((resolve) => setTimeout(resolve, ms));
  62. }
  63. static padNumber(num, length): string {
  64. let str = num.toString();
  65. while (str.length < length) {
  66. str = "0" + str;
  67. }
  68. return str;
  69. }
  70. static log(o: any) {
  71. if ("1" == this.getQueryString("log")) {
  72. console.log(o);
  73. }
  74. }
  75. // 创建UI
  76. static createUI(
  77. filepath: string,
  78. parent: Node = null,
  79. callback: Function = null
  80. ): Promise<Node> {
  81. return new Promise((resolve, reject) => {
  82. resources.load(filepath, Prefab, (err, ret) => {
  83. log("加载UI:" + filepath);
  84. if (err) {
  85. console.error(err);
  86. reject();
  87. return;
  88. }
  89. if (parent == null) {
  90. parent = find("Canvas/popupNode");
  91. }
  92. let index = filepath.lastIndexOf("/");
  93. let name = filepath.substr(index + 1, filepath.length - index);
  94. if (parent.getComponentInChildren(name)) {
  95. console.log("重复UI跳过");
  96. return;
  97. }
  98. var tmp: Node = instantiate(ret);
  99. tmp.parent = parent;
  100. if (callback) callback(tmp);
  101. resolve(tmp);
  102. });
  103. });
  104. }
  105. // 创建预制体
  106. static createPrefab(
  107. filepath: string,
  108. parent: Node = null,
  109. callback: Function = null,
  110. pos: Vec3 = null
  111. ) {
  112. return new Promise((resolve, reject) => {
  113. resources.load(filepath, Prefab, (err, ret) => {
  114. if (err) {
  115. console.error(err);
  116. reject();
  117. return;
  118. }
  119. if (parent == null) {
  120. parent = find("Canvas");
  121. }
  122. var tmp: Node = instantiate(ret);
  123. // tmp.opacity = 0;
  124. // tmp.runAction(
  125. // sequence(
  126. // delayTime(0.01),
  127. // callFunc(() => {
  128. // tmp.opacity = 255;
  129. // })
  130. // )
  131. // );
  132. tmp.parent = parent;
  133. if (pos) {
  134. tmp.position = pos;
  135. }
  136. if (callback) callback(tmp);
  137. resolve(tmp);
  138. });
  139. });
  140. }
  141. // 获取随机数
  142. public static getRandom(lower, upper): number {
  143. return Math.random() * (upper - lower) + lower;
  144. }
  145. // 获取随机整数
  146. public static getRandomInt(lower, upper): number {
  147. return Math.floor(Math.random() * (upper - lower)) + lower;
  148. }
  149. // 获取随机整数
  150. public static seedRandomInt(lower, upper): number {
  151. return Utils.getRandomInt(lower, upper);
  152. }
  153. // 格式化数字
  154. public static formatNumber(num: number, afterdot: number = 2): string {
  155. num = Number(num);
  156. num = Number(num.toFixed(afterdot));
  157. if (num < 1000) {
  158. return num.toString();
  159. }
  160. return BigNumber.getLargeString(num);
  161. }
  162. public static getPowNum(p) {
  163. return Math.pow(10, p);
  164. }
  165. // 设置服务器时间
  166. public static setServerTime(time: number) {
  167. Utils.timeOffset = time - new Date().getTime();
  168. }
  169. // 获取服务器时间
  170. public static timeOffset: number = 0;
  171. public static getServerTime() {
  172. return new Date().getTime() + Utils.timeOffset;
  173. }
  174. // 添加点击事件
  175. public static addClickEvent(
  176. node,
  177. target,
  178. component,
  179. handler,
  180. customEventData
  181. ) {
  182. var eventHandler = new Component.EventHandler();
  183. eventHandler.target = target;
  184. eventHandler.component = component;
  185. eventHandler.handler = handler;
  186. if (customEventData) eventHandler.customEventData = customEventData;
  187. var clickEvents = node.getComponent(Button).clickEvents;
  188. if (clickEvents.length > 0) {
  189. // if (!EDITOR) warn("按钮已经存在绑定,跳过自动绑定", node.name);
  190. return;
  191. }
  192. console.log(node.name, target.name, component);
  193. clickEvents.push(eventHandler);
  194. }
  195. public static secondsToDHMS(seconds) {
  196. if (seconds <= 0) {
  197. return `00d 00h:00m:00s`;
  198. }
  199. const days = Math.floor(seconds / (24 * 3600));
  200. let d = days < 9 ? "0" + days : days;
  201. const hours = Math.floor((seconds % (24 * 3600)) / 3600);
  202. let h = hours < 9 ? "0" + hours : hours;
  203. const minutes = Math.floor((seconds % 3600) / 60);
  204. let min = minutes < 9 ? "0" + minutes : minutes;
  205. const second = Math.floor(seconds % 60);
  206. let s = second < 9 ? "0" + second : second;
  207. let str = days > 0 ? `${d}d ${h}h:${min}m:${s}s` : `${h}h:${min}m:${s}s`;
  208. return str;
  209. }
  210. public static formatTimestamp(timestamp) {}
  211. // 秒转换为时分秒
  212. public static getToTimeByS(second: number) {
  213. const totalSeconds = Math.floor(second);
  214. const hours = Math.floor(totalSeconds / 3600) % 24;
  215. const minutes = Math.floor((totalSeconds % 3600) / 60);
  216. const seconds = totalSeconds % 60;
  217. return [hours, minutes, seconds]
  218. .map((unit) => (unit < 10 ? "0" + unit : unit.toString())) // 确保每个部分都是两位数
  219. .join(":"); // 拼接为字符串
  220. }
  221. private static padStartCustom(str, targetLength, padString) {
  222. str = String(str);
  223. padString = String(padString);
  224. if (str.length >= targetLength) {
  225. return str;
  226. }
  227. targetLength -= str.length;
  228. if (targetLength > padString.length) {
  229. padString = padString.repeat(Math.ceil(targetLength / padString.length));
  230. }
  231. return padString.slice(0, targetLength) + str;
  232. }
  233. public static formateDateRemaining(targetTimestamp: number) {
  234. const now = Utils.getServerTime();
  235. const remainingTime = targetTimestamp - now;
  236. const days = Math.floor(remainingTime / (1000 * 60 * 60 * 24));
  237. const hours = Math.floor(
  238. (remainingTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
  239. );
  240. const minutes = Math.floor(
  241. (remainingTime % (1000 * 60 * 60)) / (1000 * 60)
  242. );
  243. const seconds = Math.floor((remainingTime % (1000 * 60)) / 1000);
  244. const dStr = days < 10 ? "0" + days : days;
  245. const hStr = hours < 10 ? "0" + hours : hours;
  246. const mStr = minutes < 10 ? "0" + minutes : minutes;
  247. const sStr = seconds < 10 ? "0" + seconds : seconds;
  248. return `${dStr}d ${hStr}h:${mStr}m:${sStr}s`;
  249. }
  250. public static formatTimeRemaining(targetTimestamp) {
  251. // 获取当前时间戳
  252. const now = Date.now();
  253. // 计算剩余时间的毫秒数
  254. let remainingTime = targetTimestamp - now;
  255. // 如果剩余时间小于等于 0,表示目标时间已经过去
  256. if (remainingTime <= 0) {
  257. return "00:00:00";
  258. }
  259. // 计算小时、分钟、秒
  260. const hours = Math.floor(remainingTime / (1000 * 60 * 60));
  261. remainingTime -= hours * 1000 * 60 * 60;
  262. const minutes = Math.floor(remainingTime / (1000 * 60));
  263. remainingTime -= minutes * 1000 * 60;
  264. const seconds = Math.floor(remainingTime / 1000);
  265. // 格式化为两位数的字符串
  266. const formattedHours = Utils.padStartCustom(hours, 2, "0");
  267. const formattedMinutes = Utils.padStartCustom(minutes, 2, "0");
  268. const formattedSeconds = Utils.padStartCustom(seconds, 2, "0");
  269. return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
  270. }
  271. // 秒转换为时分秒
  272. public static getTimeStrByS(second: number) {
  273. second = Math.floor(second);
  274. if (second < 0) second = 0;
  275. var d = Math.floor(second / 3600 / 24);
  276. second -= d * 3600 * 24;
  277. var h = Math.floor(second / 3600);
  278. second -= h * 3600;
  279. var m = Math.floor(second / 60);
  280. second -= m * 60;
  281. var front = "00";
  282. if (h > 9) {
  283. front = "" + h;
  284. } else {
  285. front = "0" + h;
  286. }
  287. var mid = "00";
  288. if (m > 9) {
  289. mid = "" + m;
  290. } else {
  291. mid = "0" + m;
  292. }
  293. var back = "00";
  294. if (second > 9) {
  295. back = "" + second;
  296. } else {
  297. back = "0" + second;
  298. }
  299. if (d > 0) {
  300. return d + ":" + h + ":" + m;
  301. } else {
  302. var longTime = h > 0;
  303. if (longTime) {
  304. return front + ":" + mid;
  305. } else {
  306. return mid + ":" + back; //+ '秒';
  307. }
  308. }
  309. }
  310. public static formattedDate(timestamp) {
  311. const date = new Date(Number(timestamp));
  312. // 获取年月日
  313. const year = date.getFullYear();
  314. let month = date.getMonth() + 1;
  315. let monthS = month < 9 ? "0" + month : month;
  316. let day = date.getDate();
  317. let dayS = day < 9 ? "0" + day : day;
  318. const hours = date.getHours();
  319. let hoursS = hours < 9 ? "0" + hours : hours;
  320. const minutes = date.getMinutes();
  321. let minutesS = minutes < 9 ? "0" + minutes : minutes;
  322. const seconds = date.getSeconds();
  323. let secondsS = seconds < 9 ? "0" + seconds : seconds;
  324. const formattedDate = `${year}-${monthS}-${dayS} ${hoursS}:${minutesS}:${secondsS}`;
  325. return formattedDate;
  326. }
  327. public static formattedOnlyDate(timestamp) {
  328. const date = new Date(Number(timestamp));
  329. // 获取年月日
  330. const year = date.getFullYear();
  331. let month = date.getMonth() + 1;
  332. let monthS = month < 9 ? "0" + month : month;
  333. let day = date.getDate();
  334. let dayS = day < 9 ? "0" + day : day;
  335. const formattedDate = `${year}-${monthS}-${dayS}`;
  336. return formattedDate;
  337. }
  338. // 格式化金币
  339. public static formatCoin(num: number) {
  340. num = Math.floor(num);
  341. return BigNumber.getLargeString(num);
  342. }
  343. // 加载资源
  344. public static loadRes(path: string, type: typeof Asset): Promise<Asset> {
  345. return new Promise((resolve, reject) => {
  346. resources.load(path, type, (err, ret) => {
  347. if (err) {
  348. error(path, err);
  349. reject(null);
  350. } else {
  351. resolve(ret);
  352. }
  353. });
  354. });
  355. }
  356. public static loadRemote(path: string, type: typeof ImageAsset) {
  357. return new Promise((resolve, reject) => {
  358. assetManager.loadRemote(path, type, (err, ret) => {
  359. if (err) {
  360. error(path, err);
  361. reject(null);
  362. } else {
  363. resolve(ret);
  364. }
  365. });
  366. });
  367. }
  368. // 权重
  369. public static weight(v: number[]): number {
  370. var mTotalWeight = 0;
  371. for (var i = 0; i < v.length; ++i) {
  372. mTotalWeight += v[i];
  373. }
  374. if (mTotalWeight <= 0) return -1;
  375. var randnum = Math.round(Math.random() * Number.MAX_VALUE) % mTotalWeight;
  376. for (var i = 0; i < v.length; ++i) {
  377. if (randnum < v[i]) {
  378. return i;
  379. } else {
  380. randnum -= v[i];
  381. }
  382. }
  383. return -1;
  384. }
  385. //定点数
  386. public static fixFloat(val: number, count: number = 2) {
  387. var a = count * 100;
  388. return Math.floor(val * a) / a;
  389. }
  390. // 在子节点中查找
  391. private static _findInChildren(node: Node, name: string): Node {
  392. var x = node.getChildByName(name);
  393. if (x) return x;
  394. if (node.children.length == 0) return null;
  395. for (var i = 0; i < node.children.length; ++i) {
  396. var tmp = this._findInChildren(node.children[i], name);
  397. if (tmp) return tmp;
  398. }
  399. return null;
  400. }
  401. // 飞动动画
  402. public static flyAnim(
  403. type: number,
  404. startNode: Node,
  405. targetNodeName: string,
  406. count: number,
  407. radius: number,
  408. callback: Function
  409. ) {
  410. let srcNode = this._findInChildren(director.getScene(), targetNodeName);
  411. if (!srcNode) {
  412. notPlay = false;
  413. callback();
  414. return;
  415. }
  416. let getPoint = (r, ox, oy, count) => {
  417. var point = []; //结果
  418. var radians = (Math.PI / 180) * Math.round(360 / count), //弧度
  419. i = 0;
  420. for (; i < count; i++) {
  421. var x = ox + r * Math.sin(radians * i),
  422. y = oy + r * Math.cos(radians * i);
  423. point.unshift(v2(x, y)); //为保持数据顺时针
  424. }
  425. return point;
  426. };
  427. let createNode = (type) => {
  428. if (type == 0) return PoolMgr.Instance().get("Coin");
  429. if (type == 1) return PoolMgr.Instance().get("Gem");
  430. if (type == 2) return PoolMgr.Instance().get("Zp");
  431. if (type == 3) return PoolMgr.Instance().get("ZpGold");
  432. };
  433. let start = startNode.parent
  434. .getComponent(UITransform)
  435. .convertToWorldSpaceAR(startNode.position);
  436. start = find("Canvas/flyNode")
  437. .getComponent(UITransform)
  438. .convertToNodeSpaceAR(start);
  439. var array = getPoint(radius, start.x, start.y, count);
  440. var nodeArray = new Array();
  441. for (var i = 0; i < array.length; i++) {
  442. var gold = createNode(type);
  443. gold.parent = find("Canvas/flyNode");
  444. var randPos = v2(
  445. array[i].x + Utils.getRandomInt(0, 50),
  446. array[i].y + Utils.getRandomInt(0, 50)
  447. );
  448. gold.setPosition(start);
  449. nodeArray.push({ gold, randPos });
  450. }
  451. var notPlay = false;
  452. let dstPos = srcNode.parent
  453. .getComponent(UITransform)
  454. .convertToWorldSpaceAR(srcNode.position);
  455. dstPos = find("Canvas/flyNode")
  456. .getComponent(UITransform)
  457. .convertToNodeSpaceAR(dstPos);
  458. var targetGoldNode = srcNode;
  459. for (var i = 0; i < nodeArray.length; i++) {
  460. var pos = nodeArray[i].randPos;
  461. var node = nodeArray[i].gold;
  462. nodeArray[i].gold.id = i;
  463. tween(node)
  464. .to(0.2, { position: pos })
  465. .delay(i * 0.03)
  466. .to(0.5, { position: v2(dstPos.x, dstPos.y) })
  467. .call(() => {
  468. if (!notPlay) {
  469. targetGoldNode.scale = v3(1, 1, 1);
  470. notPlay = true;
  471. tween(targetGoldNode)
  472. .to(0.1, { scale: v3(2, 2, 2) })
  473. .to(0.1, { scale: v3(1, 1, 1) })
  474. .call(() => {
  475. notPlay = false;
  476. });
  477. }
  478. callback(node._id == nodeArray.length - 1);
  479. PoolMgr.Instance().put(node.name, node);
  480. if (node._id == nodeArray.length - 1) {
  481. find("Canvas/flyNode").removeAllChildren();
  482. }
  483. })
  484. .start();
  485. }
  486. }
  487. /**
  488. * 深度复制
  489. * @param value
  490. */
  491. public static deepClone(value: any) {
  492. // 处理基本数据类型和特殊情况
  493. if (value === null || typeof value !== "object") {
  494. return value; // 基本数据类型和 null
  495. }
  496. // 处理数组
  497. if (Array.isArray(value)) {
  498. return value.map((item) => Utils.deepClone(item)); // 递归拷贝每个元素
  499. }
  500. // 处理对象
  501. const copy = {};
  502. for (const key in value) {
  503. if (value.hasOwnProperty(key)) {
  504. copy[key] = Utils.deepClone(value[key]); // 递归拷贝每个属性
  505. }
  506. }
  507. return copy;
  508. }
  509. /**
  510. * 获取url参数
  511. * @param name
  512. * @returns
  513. */
  514. public static getQueryString(name: any) {
  515. var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
  516. var r = window.location.search.substr(1).match(reg);
  517. if (r != null) return unescape(r[2]);
  518. return null;
  519. }
  520. /**
  521. * 贝塞尔曲线计算
  522. * @param points
  523. * @param t
  524. */
  525. public static calculateBezier(points: Vec2[], t: number) {
  526. const n = points.length;
  527. if (n === 0) return v2(0, 0); // 如果没有控制点,返回 (0,0)
  528. if (n === 1) return points[0]; // 如果只有一个控制点,返回该点
  529. let tempPoints = points.slice(); // 创建控制点的副本
  530. // De Casteljau's algorithm
  531. for (let r = 1; r < n; r++) {
  532. for (let i = 0; i < n - r; i++) {
  533. tempPoints[i] = v2(
  534. (1 - t) * tempPoints[i].x + t * tempPoints[i + 1].x,
  535. (1 - t) * tempPoints[i].y + t * tempPoints[i + 1].y
  536. );
  537. }
  538. }
  539. return tempPoints[0]; // 返回计算得到的点
  540. }
  541. /**
  542. * 是否为某个链接
  543. * @param url
  544. * @param hostname
  545. * @returns
  546. */
  547. static isNameLink(url: string | URL, hostname: string) {
  548. try {
  549. const parsedUrl = new URL(url);
  550. return parsedUrl.hostname === hostname;
  551. } catch (e) {
  552. console.error("Invalid URL");
  553. return false;
  554. }
  555. }
  556. // 将用户 ID 转换为 26 进制字符串
  557. public static convertTo26(userId) {
  558. let sb = "";
  559. while (userId > 0) {
  560. userId--;
  561. let ch = String.fromCharCode((userId % 26) + "A".charCodeAt(0));
  562. sb = ch + sb;
  563. userId = Math.floor(userId / 26);
  564. }
  565. return sb;
  566. }
  567. // 生成邀请代码
  568. public static generateInviteCode(channelId, userId) {
  569. return channelId + this.convertTo26(userId);
  570. }
  571. // 取后缀
  572. public static getSuffix(idx) {
  573. idx = Math.floor(idx);
  574. if (idx <= 0) {
  575. return "";
  576. }
  577. idx = idx - 1;
  578. let result = "";
  579. while (idx >= 0) {
  580. result = String.fromCharCode((idx % 26) + 97) + result;
  581. idx = Math.floor(idx / 26) - 1;
  582. }
  583. result = "";
  584. return result;
  585. }
  586. // max: 10 ==> 1->1,2->2,..10->10,11->1
  587. public static getMappedValue(idx, max) {
  588. let result;
  589. result = idx % max == 0 ? max : idx % max;
  590. return result;
  591. }
  592. //
  593. public static getStarValue(idx, max) {
  594. let result = Math.floor((idx - 1) / max);
  595. return result;
  596. }
  597. // 格式化地址
  598. static formAddress(address: string) {
  599. if (
  600. address == null ||
  601. address == "" ||
  602. address == undefined ||
  603. address.length < 10
  604. ) {
  605. return " ";
  606. }
  607. return (
  608. address.substring(0, 8) +
  609. "..." +
  610. address.substring(address.length - 8, address.length)
  611. );
  612. }
  613. //
  614. public static updateLabelText(fullText, maxWidth, fontSize) {
  615. let str = "";
  616. // 计算文本的实际宽度
  617. let textWidth = this.getTextWidth(fullText, fontSize);
  618. if (textWidth > maxWidth) {
  619. // 超过最大宽度,显示截断后的文本并加上省略号
  620. let truncatedText = this.truncateText(fullText, maxWidth, fontSize);
  621. str = truncatedText + "...";
  622. } else {
  623. // 显示完整文本
  624. str = fullText;
  625. }
  626. return str;
  627. }
  628. // 获取文本的宽度
  629. private static getTextWidth(text, fontSize) {
  630. let canvas = document.createElement("canvas");
  631. let ctx = canvas.getContext("2d");
  632. ctx.font = fontSize + "px Arial"; // 设置字体大小
  633. return ctx.measureText(text).width;
  634. }
  635. // 截断文本并返回适合的文本
  636. private static truncateText(text, maxWidth, fontSize) {
  637. let i = 0;
  638. let truncatedText = text;
  639. // 循环截取字符直到文本超出最大宽度
  640. while (
  641. this.getTextWidth(truncatedText, fontSize) > maxWidth &&
  642. i < text.length
  643. ) {
  644. i++;
  645. truncatedText = text.slice(0, text.length - i);
  646. }
  647. return truncatedText;
  648. }
  649. // 多语言
  650. static setI18nLabel(str: string, value?: string, value2?: string) {
  651. let i18nStr = str;
  652. i18nStr = LanguageManager.getText(str);
  653. if (value) {
  654. i18nStr = i18nStr.replace("{value}", value);
  655. }
  656. if (value2) {
  657. i18nStr = i18nStr.replace("{value2}", value2);
  658. }
  659. return i18nStr;
  660. }
  661. }