@etsoo/appscript 1.4.73 → 1.4.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { INotifier, NotificationAlign, NotificationCallProps, NotificationContent, NotificationReturn } from '@etsoo/notificationbase';
2
2
  import { ApiDataError, IApi, IPData } from '@etsoo/restclient';
3
- import { DataTypes, DateUtils, IActionResult, IStorage, ListType, ListType1 } from '@etsoo/shared';
3
+ import { DataTypes, DateUtils, ErrorData, IActionResult, IStorage, ListType, ListType1 } from '@etsoo/shared';
4
4
  import { AddressRegion } from '../address/AddressRegion';
5
5
  import { EntityStatus } from '../business/EntityStatus';
6
6
  import { InitCallDto } from '../erp/dto/InitCallDto';
@@ -189,6 +189,11 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
189
189
  * @param api Api
190
190
  */
191
191
  setApiLoading(api: IApi): void;
192
+ /**
193
+ * Setup frontend logging
194
+ * @param action Custom action
195
+ */
196
+ setupLogging(action?: (data: ErrorData) => void | Promise<void>): void;
192
197
  /**
193
198
  * Api init call
194
199
  * @param data Data
@@ -306,6 +306,7 @@ class CoreApp {
306
306
  ? api.transformResponse(error.response).status
307
307
  : undefined;
308
308
  if (status === 401) {
309
+ // Unauthorized
309
310
  if (handlerFor401 === false)
310
311
  return;
311
312
  if (typeof handlerFor401 === 'function') {
@@ -316,6 +317,17 @@ class CoreApp {
316
317
  }
317
318
  return;
318
319
  }
320
+ else if (error.response == null &&
321
+ (error.message === 'Network Error' ||
322
+ error.message === 'Failed to fetch')) {
323
+ // Network error
324
+ this.notifier.alert(this.get('networkError'));
325
+ return;
326
+ }
327
+ else {
328
+ // Log
329
+ console.error('API error', error);
330
+ }
319
331
  // Report the error
320
332
  this.notifier.alert(this.formatError(error));
321
333
  };
@@ -339,6 +351,23 @@ class CoreApp {
339
351
  this.lastCalled = true;
340
352
  };
341
353
  }
354
+ /**
355
+ * Setup frontend logging
356
+ * @param action Custom action
357
+ */
358
+ setupLogging(action) {
359
+ action !== null && action !== void 0 ? action : (action = (data) => {
360
+ this.api.post('Auth/LogFrontendError', data, {
361
+ onError: (error) => {
362
+ // Use 'debug' to avoid infinite loop
363
+ console.debug('Log front-end error', data, error);
364
+ // Prevent global error handler
365
+ return false;
366
+ }
367
+ });
368
+ });
369
+ shared_1.DomUtils.setupLogging(action);
370
+ }
342
371
  /**
343
372
  * Api init call
344
373
  * @param data Data
@@ -650,7 +679,7 @@ class CoreApp {
650
679
  const iv = enc.Hex.parse(messageEncrypted.substring(34, 66));
651
680
  const encrypted = messageEncrypted.substring(66);
652
681
  const key = PBKDF2(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, salt, {
653
- keySize: 8,
682
+ keySize: 8, // 256 / 32
654
683
  hasher: algo.SHA256,
655
684
  iterations: 1000 * iterations
656
685
  });
@@ -661,7 +690,7 @@ class CoreApp {
661
690
  }).toString(enc.Utf8);
662
691
  }
663
692
  catch (e) {
664
- console.log('decrypt', e);
693
+ console.error(`CoreApp.decrypt ${messageEncrypted} error`, e);
665
694
  return undefined;
666
695
  }
667
696
  }
@@ -696,7 +725,7 @@ class CoreApp {
696
725
  return this.decrypt(message, passphrase);
697
726
  }
698
727
  catch (e) {
699
- console.log('decryptEnhanced', e);
728
+ console.error(`CoreApp.decryptEnhanced ${messageEncrypted} error`, e);
700
729
  return undefined;
701
730
  }
702
731
  }
@@ -776,7 +805,7 @@ class CoreApp {
776
805
  const bits = 16; // 128 / 8
777
806
  const salt = lib.WordArray.random(bits);
778
807
  const key = PBKDF2(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, salt, {
779
- keySize: 8,
808
+ keySize: 8, // 256 / 32
780
809
  hasher: algo.SHA256,
781
810
  iterations: 1000 * iterations
782
811
  });
@@ -864,7 +893,7 @@ class CoreApp {
864
893
  * @returns Error message
865
894
  */
866
895
  formatError(error) {
867
- return error.toString();
896
+ return `${error.message} (${error.name})`;
868
897
  }
869
898
  /**
870
899
  * Refresh token failed
@@ -1311,7 +1340,7 @@ class CoreApp {
1311
1340
  async signout() {
1312
1341
  await this.api.put('User/Signout', { deviceId: this.deviceId }, {
1313
1342
  onError: (error) => {
1314
- console.log(error);
1343
+ console.error('CoreApp.signout error', error);
1315
1344
  // Prevent further processing
1316
1345
  return false;
1317
1346
  }
@@ -1,6 +1,6 @@
1
1
  import { INotifier, NotificationAlign, NotificationCallProps, NotificationContent, NotificationReturn } from '@etsoo/notificationbase';
2
2
  import { ApiDataError, IApi, IPData } from '@etsoo/restclient';
3
- import { DataTypes, DateUtils, IActionResult, IStorage, ListType, ListType1 } from '@etsoo/shared';
3
+ import { DataTypes, DateUtils, ErrorData, IActionResult, IStorage, ListType, ListType1 } from '@etsoo/shared';
4
4
  import { AddressRegion } from '../address/AddressRegion';
5
5
  import { IUser } from '../state/User';
6
6
  import { IAppSettings } from './AppSettings';
@@ -459,6 +459,11 @@ export interface IApp {
459
459
  * @param api Api
460
460
  */
461
461
  setApiLoading(api: IApi): void;
462
+ /**
463
+ * Setup frontend logging
464
+ * @param action Custom action
465
+ */
466
+ setupLogging(action?: (data: ErrorData) => void | Promise<void>): void;
462
467
  /**
463
468
  * Signout, with userLogout and toLoginPage
464
469
  */
@@ -31,7 +31,7 @@ class ShoppingCart {
31
31
  storage.setPersistedData(identifier, null);
32
32
  }
33
33
  catch (error) {
34
- console.log('ShoppingCart clear', error);
34
+ console.warn(`ShoppingCart clear ${identifier} error`, error);
35
35
  }
36
36
  }
37
37
  /**
@@ -47,7 +47,7 @@ class ShoppingCart {
47
47
  return ((_a = storage.getPersistedObject(id)) !== null && _a !== void 0 ? _a : storage.getObject(id));
48
48
  }
49
49
  catch (error) {
50
- console.log('ShoppingCart constructor', error);
50
+ console.warn(`ShoppingCart getCartData ${id} error`, error);
51
51
  }
52
52
  }
53
53
  /**
@@ -345,7 +345,7 @@ class ShoppingCart {
345
345
  }
346
346
  }
347
347
  catch (error) {
348
- console.log('ShoppingCart save', error);
348
+ console.warn(`ShoppingCart save ${this.identifier} error`, error);
349
349
  }
350
350
  return data;
351
351
  }
@@ -110,6 +110,7 @@
110
110
  "moreTag": "{0} more",
111
111
  "name": "Name",
112
112
  "nameB": "Name",
113
+ "networkError": "The local network is faulty and cannot connect to the remote server",
113
114
  "newPassword": "New password",
114
115
  "newPasswordRequired": "Please enter your new password",
115
116
  "newPasswordTip": "New password should be different",
@@ -110,6 +110,7 @@
110
110
  "moreTag": "({0}+)",
111
111
  "name": "姓名",
112
112
  "nameB": "名称",
113
+ "networkError": "本地网络故障,无法链接到远程服务器",
113
114
  "newPassword": "新密码",
114
115
  "newPasswordRequired": "请输入新密码",
115
116
  "newPasswordTip": "请设置一个和现在的密码不一样的新密码",
@@ -110,6 +110,7 @@
110
110
  "moreTag": "({0}+)",
111
111
  "name": "姓名",
112
112
  "nameB": "名稱",
113
+ "networkError": "本地網路故障,無法連結到遠端伺服器",
113
114
  "newPassword": "新密碼",
114
115
  "newPasswordRequired": "請輸入新密碼",
115
116
  "newPasswordTip": "請設置一個和現在的密碼不一樣的新密碼",
@@ -1,6 +1,6 @@
1
1
  import { INotifier, NotificationAlign, NotificationCallProps, NotificationContent, NotificationReturn } from '@etsoo/notificationbase';
2
2
  import { ApiDataError, IApi, IPData } from '@etsoo/restclient';
3
- import { DataTypes, DateUtils, IActionResult, IStorage, ListType, ListType1 } from '@etsoo/shared';
3
+ import { DataTypes, DateUtils, ErrorData, IActionResult, IStorage, ListType, ListType1 } from '@etsoo/shared';
4
4
  import { AddressRegion } from '../address/AddressRegion';
5
5
  import { EntityStatus } from '../business/EntityStatus';
6
6
  import { InitCallDto } from '../erp/dto/InitCallDto';
@@ -189,6 +189,11 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
189
189
  * @param api Api
190
190
  */
191
191
  setApiLoading(api: IApi): void;
192
+ /**
193
+ * Setup frontend logging
194
+ * @param action Custom action
195
+ */
196
+ setupLogging(action?: (data: ErrorData) => void | Promise<void>): void;
192
197
  /**
193
198
  * Api init call
194
199
  * @param data Data
@@ -280,6 +280,7 @@ export class CoreApp {
280
280
  ? api.transformResponse(error.response).status
281
281
  : undefined;
282
282
  if (status === 401) {
283
+ // Unauthorized
283
284
  if (handlerFor401 === false)
284
285
  return;
285
286
  if (typeof handlerFor401 === 'function') {
@@ -290,6 +291,17 @@ export class CoreApp {
290
291
  }
291
292
  return;
292
293
  }
294
+ else if (error.response == null &&
295
+ (error.message === 'Network Error' ||
296
+ error.message === 'Failed to fetch')) {
297
+ // Network error
298
+ this.notifier.alert(this.get('networkError'));
299
+ return;
300
+ }
301
+ else {
302
+ // Log
303
+ console.error('API error', error);
304
+ }
293
305
  // Report the error
294
306
  this.notifier.alert(this.formatError(error));
295
307
  };
@@ -313,6 +325,23 @@ export class CoreApp {
313
325
  this.lastCalled = true;
314
326
  };
315
327
  }
328
+ /**
329
+ * Setup frontend logging
330
+ * @param action Custom action
331
+ */
332
+ setupLogging(action) {
333
+ action !== null && action !== void 0 ? action : (action = (data) => {
334
+ this.api.post('Auth/LogFrontendError', data, {
335
+ onError: (error) => {
336
+ // Use 'debug' to avoid infinite loop
337
+ console.debug('Log front-end error', data, error);
338
+ // Prevent global error handler
339
+ return false;
340
+ }
341
+ });
342
+ });
343
+ DomUtils.setupLogging(action);
344
+ }
316
345
  /**
317
346
  * Api init call
318
347
  * @param data Data
@@ -624,7 +653,7 @@ export class CoreApp {
624
653
  const iv = enc.Hex.parse(messageEncrypted.substring(34, 66));
625
654
  const encrypted = messageEncrypted.substring(66);
626
655
  const key = PBKDF2(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, salt, {
627
- keySize: 8,
656
+ keySize: 8, // 256 / 32
628
657
  hasher: algo.SHA256,
629
658
  iterations: 1000 * iterations
630
659
  });
@@ -635,7 +664,7 @@ export class CoreApp {
635
664
  }).toString(enc.Utf8);
636
665
  }
637
666
  catch (e) {
638
- console.log('decrypt', e);
667
+ console.error(`CoreApp.decrypt ${messageEncrypted} error`, e);
639
668
  return undefined;
640
669
  }
641
670
  }
@@ -670,7 +699,7 @@ export class CoreApp {
670
699
  return this.decrypt(message, passphrase);
671
700
  }
672
701
  catch (e) {
673
- console.log('decryptEnhanced', e);
702
+ console.error(`CoreApp.decryptEnhanced ${messageEncrypted} error`, e);
674
703
  return undefined;
675
704
  }
676
705
  }
@@ -750,7 +779,7 @@ export class CoreApp {
750
779
  const bits = 16; // 128 / 8
751
780
  const salt = lib.WordArray.random(bits);
752
781
  const key = PBKDF2(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, salt, {
753
- keySize: 8,
782
+ keySize: 8, // 256 / 32
754
783
  hasher: algo.SHA256,
755
784
  iterations: 1000 * iterations
756
785
  });
@@ -838,7 +867,7 @@ export class CoreApp {
838
867
  * @returns Error message
839
868
  */
840
869
  formatError(error) {
841
- return error.toString();
870
+ return `${error.message} (${error.name})`;
842
871
  }
843
872
  /**
844
873
  * Refresh token failed
@@ -1285,7 +1314,7 @@ export class CoreApp {
1285
1314
  async signout() {
1286
1315
  await this.api.put('User/Signout', { deviceId: this.deviceId }, {
1287
1316
  onError: (error) => {
1288
- console.log(error);
1317
+ console.error('CoreApp.signout error', error);
1289
1318
  // Prevent further processing
1290
1319
  return false;
1291
1320
  }
@@ -1,6 +1,6 @@
1
1
  import { INotifier, NotificationAlign, NotificationCallProps, NotificationContent, NotificationReturn } from '@etsoo/notificationbase';
2
2
  import { ApiDataError, IApi, IPData } from '@etsoo/restclient';
3
- import { DataTypes, DateUtils, IActionResult, IStorage, ListType, ListType1 } from '@etsoo/shared';
3
+ import { DataTypes, DateUtils, ErrorData, IActionResult, IStorage, ListType, ListType1 } from '@etsoo/shared';
4
4
  import { AddressRegion } from '../address/AddressRegion';
5
5
  import { IUser } from '../state/User';
6
6
  import { IAppSettings } from './AppSettings';
@@ -459,6 +459,11 @@ export interface IApp {
459
459
  * @param api Api
460
460
  */
461
461
  setApiLoading(api: IApi): void;
462
+ /**
463
+ * Setup frontend logging
464
+ * @param action Custom action
465
+ */
466
+ setupLogging(action?: (data: ErrorData) => void | Promise<void>): void;
462
467
  /**
463
468
  * Signout, with userLogout and toLoginPage
464
469
  */
@@ -28,7 +28,7 @@ export class ShoppingCart {
28
28
  storage.setPersistedData(identifier, null);
29
29
  }
30
30
  catch (error) {
31
- console.log('ShoppingCart clear', error);
31
+ console.warn(`ShoppingCart clear ${identifier} error`, error);
32
32
  }
33
33
  }
34
34
  /**
@@ -44,7 +44,7 @@ export class ShoppingCart {
44
44
  return ((_a = storage.getPersistedObject(id)) !== null && _a !== void 0 ? _a : storage.getObject(id));
45
45
  }
46
46
  catch (error) {
47
- console.log('ShoppingCart constructor', error);
47
+ console.warn(`ShoppingCart getCartData ${id} error`, error);
48
48
  }
49
49
  }
50
50
  /**
@@ -342,7 +342,7 @@ export class ShoppingCart {
342
342
  }
343
343
  }
344
344
  catch (error) {
345
- console.log('ShoppingCart save', error);
345
+ console.warn(`ShoppingCart save ${this.identifier} error`, error);
346
346
  }
347
347
  return data;
348
348
  }
@@ -110,6 +110,7 @@
110
110
  "moreTag": "{0} more",
111
111
  "name": "Name",
112
112
  "nameB": "Name",
113
+ "networkError": "The local network is faulty and cannot connect to the remote server",
113
114
  "newPassword": "New password",
114
115
  "newPasswordRequired": "Please enter your new password",
115
116
  "newPasswordTip": "New password should be different",
@@ -110,6 +110,7 @@
110
110
  "moreTag": "({0}+)",
111
111
  "name": "姓名",
112
112
  "nameB": "名称",
113
+ "networkError": "本地网络故障,无法链接到远程服务器",
113
114
  "newPassword": "新密码",
114
115
  "newPasswordRequired": "请输入新密码",
115
116
  "newPasswordTip": "请设置一个和现在的密码不一样的新密码",
@@ -110,6 +110,7 @@
110
110
  "moreTag": "({0}+)",
111
111
  "name": "姓名",
112
112
  "nameB": "名稱",
113
+ "networkError": "本地網路故障,無法連結到遠端伺服器",
113
114
  "newPassword": "新密碼",
114
115
  "newPasswordRequired": "請輸入新密碼",
115
116
  "newPasswordTip": "請設置一個和現在的密碼不一樣的新密碼",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/appscript",
3
- "version": "1.4.73",
3
+ "version": "1.4.75",
4
4
  "description": "Applications shared TypeScript framework",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -52,22 +52,22 @@
52
52
  },
53
53
  "homepage": "https://github.com/ETSOO/AppScript#readme",
54
54
  "dependencies": {
55
- "@etsoo/notificationbase": "^1.1.32",
56
- "@etsoo/restclient": "^1.0.95",
57
- "@etsoo/shared": "^1.2.22",
55
+ "@etsoo/notificationbase": "^1.1.35",
56
+ "@etsoo/restclient": "^1.0.98",
57
+ "@etsoo/shared": "^1.2.26",
58
58
  "crypto-js": "^4.2.0"
59
59
  },
60
60
  "devDependencies": {
61
- "@babel/cli": "^7.23.0",
62
- "@babel/core": "^7.23.3",
63
- "@babel/plugin-transform-runtime": "^7.23.3",
64
- "@babel/preset-env": "^7.23.3",
65
- "@babel/runtime-corejs3": "^7.23.2",
66
- "@types/crypto-js": "^4.2.1",
67
- "@types/jest": "^29.5.8",
61
+ "@babel/cli": "^7.23.9",
62
+ "@babel/core": "^7.23.9",
63
+ "@babel/plugin-transform-runtime": "^7.23.9",
64
+ "@babel/preset-env": "^7.23.9",
65
+ "@babel/runtime-corejs3": "^7.23.9",
66
+ "@types/crypto-js": "^4.2.2",
67
+ "@types/jest": "^29.5.12",
68
68
  "jest": "^29.7.0",
69
69
  "jest-environment-jsdom": "^29.7.0",
70
- "ts-jest": "^29.1.1",
71
- "typescript": "^5.2.2"
70
+ "ts-jest": "^29.1.2",
71
+ "typescript": "^5.3.3"
72
72
  }
73
73
  }
@@ -12,6 +12,7 @@ import {
12
12
  DataTypes,
13
13
  DateUtils,
14
14
  DomUtils,
15
+ ErrorData,
15
16
  IActionResult,
16
17
  IStorage,
17
18
  ListType,
@@ -469,6 +470,7 @@ export abstract class CoreApp<
469
470
  : undefined;
470
471
 
471
472
  if (status === 401) {
473
+ // Unauthorized
472
474
  if (handlerFor401 === false) return;
473
475
  if (typeof handlerFor401 === 'function') {
474
476
  handlerFor401();
@@ -476,6 +478,17 @@ export abstract class CoreApp<
476
478
  this.tryLogin();
477
479
  }
478
480
  return;
481
+ } else if (
482
+ error.response == null &&
483
+ (error.message === 'Network Error' ||
484
+ error.message === 'Failed to fetch')
485
+ ) {
486
+ // Network error
487
+ this.notifier.alert(this.get('networkError')!);
488
+ return;
489
+ } else {
490
+ // Log
491
+ console.error('API error', error);
479
492
  }
480
493
 
481
494
  // Report the error
@@ -504,6 +517,25 @@ export abstract class CoreApp<
504
517
  };
505
518
  }
506
519
 
520
+ /**
521
+ * Setup frontend logging
522
+ * @param action Custom action
523
+ */
524
+ public setupLogging(action?: (data: ErrorData) => void | Promise<void>) {
525
+ action ??= (data) => {
526
+ this.api.post('Auth/LogFrontendError', data, {
527
+ onError: (error) => {
528
+ // Use 'debug' to avoid infinite loop
529
+ console.debug('Log front-end error', data, error);
530
+
531
+ // Prevent global error handler
532
+ return false;
533
+ }
534
+ });
535
+ };
536
+ DomUtils.setupLogging(action);
537
+ }
538
+
507
539
  /**
508
540
  * Api init call
509
541
  * @param data Data
@@ -905,7 +937,7 @@ export abstract class CoreApp<
905
937
  mode: mode.CBC
906
938
  }).toString(enc.Utf8);
907
939
  } catch (e) {
908
- console.log('decrypt', e);
940
+ console.error(`CoreApp.decrypt ${messageEncrypted} error`, e);
909
941
  return undefined;
910
942
  }
911
943
  }
@@ -952,7 +984,10 @@ export abstract class CoreApp<
952
984
 
953
985
  return this.decrypt(message, passphrase);
954
986
  } catch (e) {
955
- console.log('decryptEnhanced', e);
987
+ console.error(
988
+ `CoreApp.decryptEnhanced ${messageEncrypted} error`,
989
+ e
990
+ );
956
991
  return undefined;
957
992
  }
958
993
  }
@@ -1175,7 +1210,7 @@ export abstract class CoreApp<
1175
1210
  * @returns Error message
1176
1211
  */
1177
1212
  formatError(error: ApiDataError) {
1178
- return error.toString();
1213
+ return `${error.message} (${error.name})`;
1179
1214
  }
1180
1215
 
1181
1216
  /**
@@ -1686,7 +1721,7 @@ export abstract class CoreApp<
1686
1721
  { deviceId: this.deviceId },
1687
1722
  {
1688
1723
  onError: (error) => {
1689
- console.log(error);
1724
+ console.error('CoreApp.signout error', error);
1690
1725
  // Prevent further processing
1691
1726
  return false;
1692
1727
  }
package/src/app/IApp.ts CHANGED
@@ -9,6 +9,7 @@ import { ApiDataError, IApi, IPData } from '@etsoo/restclient';
9
9
  import {
10
10
  DataTypes,
11
11
  DateUtils,
12
+ ErrorData,
12
13
  IActionResult,
13
14
  IStorage,
14
15
  ListType,
@@ -623,6 +624,12 @@ export interface IApp {
623
624
  */
624
625
  setApiLoading(api: IApi): void;
625
626
 
627
+ /**
628
+ * Setup frontend logging
629
+ * @param action Custom action
630
+ */
631
+ setupLogging(action?: (data: ErrorData) => void | Promise<void>): void;
632
+
626
633
  /**
627
634
  * Signout, with userLogout and toLoginPage
628
635
  */
@@ -166,7 +166,7 @@ export class ShoppingCart<T extends ShoppingCartItem> {
166
166
  storage.setData(identifier, null);
167
167
  storage.setPersistedData(identifier, null);
168
168
  } catch (error) {
169
- console.log('ShoppingCart clear', error);
169
+ console.warn(`ShoppingCart clear ${identifier} error`, error);
170
170
  }
171
171
  }
172
172
 
@@ -187,7 +187,7 @@ export class ShoppingCart<T extends ShoppingCartItem> {
187
187
  storage.getObject<ShoppingCartData<D>>(id)
188
188
  );
189
189
  } catch (error) {
190
- console.log('ShoppingCart constructor', error);
190
+ console.warn(`ShoppingCart getCartData ${id} error`, error);
191
191
  }
192
192
  }
193
193
 
@@ -582,7 +582,7 @@ export class ShoppingCart<T extends ShoppingCartItem> {
582
582
  this.storage.setData(this.identifier, data);
583
583
  }
584
584
  } catch (error) {
585
- console.log('ShoppingCart save', error);
585
+ console.warn(`ShoppingCart save ${this.identifier} error`, error);
586
586
  }
587
587
 
588
588
  return data;
package/src/i18n/en.json CHANGED
@@ -110,6 +110,7 @@
110
110
  "moreTag": "{0} more",
111
111
  "name": "Name",
112
112
  "nameB": "Name",
113
+ "networkError": "The local network is faulty and cannot connect to the remote server",
113
114
  "newPassword": "New password",
114
115
  "newPasswordRequired": "Please enter your new password",
115
116
  "newPasswordTip": "New password should be different",
@@ -110,6 +110,7 @@
110
110
  "moreTag": "({0}+)",
111
111
  "name": "姓名",
112
112
  "nameB": "名称",
113
+ "networkError": "本地网络故障,无法链接到远程服务器",
113
114
  "newPassword": "新密码",
114
115
  "newPasswordRequired": "请输入新密码",
115
116
  "newPasswordTip": "请设置一个和现在的密码不一样的新密码",
@@ -110,6 +110,7 @@
110
110
  "moreTag": "({0}+)",
111
111
  "name": "姓名",
112
112
  "nameB": "名稱",
113
+ "networkError": "本地網路故障,無法連結到遠端伺服器",
113
114
  "newPassword": "新密碼",
114
115
  "newPasswordRequired": "請輸入新密碼",
115
116
  "newPasswordTip": "請設置一個和現在的密碼不一樣的新密碼",