-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.js
2859 lines (2812 loc) · 189 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// ==UserScript==
// @name screeps-chinese-pack
// @namespace http://tampermonkey.net/
// @version 1.3
// @description 用于汉化 screeps.com 网站的油猴脚本
// @author hopgoldy
// @match https://screeps.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
/**
* 要被翻译的语种
*
* 当 TranslationContent 中包含 selector 属性时,该设置将失效(因为翻译源由 selector 选择器指定了)
*/
const TRANSLATE_FROM = 'en-US';
/**
* 要翻译到的语种
*/
const TRANSLATE_TO = 'zh-CN';
/**
* 中文文档的域名
*/
const DOCUMENT_CN = 'https://screeps-cn.gitee.io';
/**
* 判断一个节点是否为 HTMLElement
*
* @param el 要判断的节点
*/
const isHTMLElement = function (el) {
return el.nodeType === Node.ELEMENT_NODE;
};
/**
* 判断一个节点是否为 Text
*
* @param el 要判断的节点
*/
const isText = function (el) {
return el.nodeType === Node.TEXT_NODE;
};
/**
* 去除 hash 中的 query 字符串
* @param hash 可能包含 query 的 hash
*/
const getNoQueryHash = function (hash) {
return hash.split('?')[0];
};
/**
* 判断一个节点是否被禁止翻译
*
* 会顺着 dom 树一直往上找,遇到设置有 stopTranslateSearch 属性的祖先节点的话就禁止翻译
*
* @param el 要进行判断的节点
* @returns 是否为被禁止翻译的节点
*/
const isExceptElement = function (el) {
if (el.stopTranslateSearch)
return true;
if (el.parentNode)
return isExceptElement(el.parentNode);
return false;
};
/**
* 将一个 html 元素及其子元素设置为禁止翻译
*
* @param selector 禁止翻译的 css 选择器
*/
const dontTranslate = function (selector) {
return {
'selector': selector,
'zh-CN': (el) => el.stopTranslateSearch = true
};
};
/**
* 去掉字符串两端的空白字符
*
* @param str 要修剪的字符串
*/
const trim = function (str) {
return str.replace(/(^\s*)|(\s*$)/g, '');
};
/**
* 多行翻译
*
* 当一个 css 选择器会选中多个元素时,就可以使用该函数快速生成一个翻译源
* 会根据传入的数据源的键值对进行翻译
*
* @param contents 多行翻译源
*/
const translateMultiple = function (contents) {
return (el) => {
const newContent = contents[trim(el.innerHTML)];
if (newContent)
el.innerHTML = newContent;
};
};
/**
* 实际的存储对象
*
* 脚本执行时访问的翻译源都保存在这里
*/
const currentPageContent = {
hash: undefined,
content: [],
queryContent: []
};
/**
* 当前使用的所有翻译数据来源
* 可以通过 updateSource 指定和更新
*/
let allPageContent = [];
/**
* HTML 元素内容缓存
*
* 会缓存上次翻译后的内容,如果下次获取元素发现没有变化就不会执行翻译
*/
const contentCache = new Map();
/**
* 获取当前的翻译源文本
*
* @return 当前使用的翻译源 [ 普通翻译对象,包含选择器的翻译对象 ]
*/
const getContent = function () {
return currentPageContent;
};
/**
* 更新翻译内容
*
* @param newContent 新的翻译内容
*/
const updateContent = function (newContent) {
// 遍历所有键尝试更新
Object.keys(newContent).forEach(key => {
// 如果没有值或者当前数据源不包含该键就不更新
if (!newContent[key] || !(key in currentPageContent))
return;
currentPageContent[key] = newContent[key];
});
};
/**
* 尝试更新翻译源文本
*
* 会去检查 hash 是否匹配,当 hash 变更(切换到了新页面)时会重新从 allPageContent 里选择翻译源
*
* @param hash 要进行翻译源匹配的 hash 值
* @param allSource 当前使用的所有翻译源
* @returns 更新后的翻译源
*/
const updateSource = function (hash, allSource) {
if (allSource)
allPageContent = allSource;
const currentHash = getNoQueryHash(hash);
// 没有变更就不进行搜索
if (currentHash === currentPageContent.hash)
return currentPageContent;
const newContent = [];
const newQueryContent = [];
// 找到所有匹配的翻译源
for (const page of allPageContent) {
const matched = page.hashs.find(pageHash => {
// 如果 hash 为空的话就精确匹配,不然太多了
if (currentHash === '')
return currentHash === pageHash;
// 有 hash 的话就进行首匹配
if (pageHash !== '')
return currentHash.startsWith(pageHash);
return false;
});
if (matched === undefined)
continue;
// 根据是否由 selector 分开存储
page.content.forEach(content => {
if (content.selector)
newQueryContent.push(content);
else
newContent.push(content);
});
}
// 更新当前存储
currentPageContent.hash = currentHash;
currentPageContent.content = newContent;
currentPageContent.queryContent = newQueryContent;
// 页面切换了,清空缓存
contentCache.clear();
return currentPageContent;
};
/**
* 递归获取该元素下所有包含内容的 text 元素
*
* @param el 要进行查询的 html 节点
* @return 包含内容的 text 元素数组
*/
const getContentElement = function (el) {
if (isHTMLElement(el)) {
// 该元素被禁止翻译了就跳过
if (el.stopTranslateSearch)
return [];
const contentElement = [];
// 遍历所有子节点递归拿到内容节点
for (let i = 0; i < el.childNodes.length; i += 1) {
const children = el.childNodes[i];
if (children.nodeType === Node.TEXT_NODE) {
// Text 节点中有很多只有换行符或者空格的,这里将其剔除掉
// 正则含义:包含除“换行”“回车”“空格”以外的其他字符
if (!/[^(\n|\r| )]/g.test(children.wholeText))
continue;
contentElement.push(children);
}
// 元素节点的话就递归继续获取(不会搜索 script 标签)
else if (isHTMLElement(children) && children.nodeName !== 'SCRIPT') {
contentElement.push(...getContentElement(children));
}
}
return contentElement;
}
// 如果是文本节点的话就直接返回
if (isText(el))
return [el];
return [];
};
/**
* 使用对应的翻译内容更新 html 元素
*
* @param el 要更新的元素
* @param content 要更新的翻译内容
*/
const updateElement = function (el, content) {
const newContent = content[TRANSLATE_TO];
if (typeof newContent === 'string')
el.innerHTML = newContent;
else if (typeof newContent === 'function')
newContent(el);
return el.innerHTML;
};
/**
* 使用对应的翻译内容更新 html 元素
*
* @param el 要更新的元素
* @param content 要更新的翻译内容
*
* @returns 翻译后的内容,若未翻译则为 undefined
*/
const updateProtectElement = function (el, content) {
const newContent = content[TRANSLATE_TO];
// 是替身节点的话就不执行任何操作
if (el.isStandNode)
return '';
el.style.display = 'none';
// 创建替身并进行基础设置
const newEl = el.cloneNode(true);
newEl.style.display = null;
newEl.isStandNode = true;
// 翻译替身
if (typeof newContent === 'string')
newEl.innerHTML = newContent;
else if (typeof newContent === 'function')
newContent(newEl);
// 更新替身
if (el.standNode)
el.parentElement.replaceChild(newEl, el.standNode);
else
el.parentElement.appendChild(newEl);
el.standNode = newEl;
// 由于某些节点可能会存在 class 变化的问题
// (比如 map 界面中房间介绍的储量,会通过加入临时的 class 来实现 in-out 效果,但是这些临时 class 会一直在替身节点身上,导致样式有问题)
// 所以这里开启一个监听,以保证替身节点的样式一致
const observer = new MutationObserver(() => {
el.standNode.className = el.className;
});
observer.observe(el, { attributes: true });
// 受保护节点会一直返回翻译前的内容
return el.innerHTML;
};
/**
* 使用对应的翻译内容更新 text 元素
*
* @param el 要更新的文本节点
* @param content 要更新的翻译内容
*/
const updateText = function (el, content) {
const newContent = content[TRANSLATE_TO];
if (typeof newContent === 'string')
el.parentElement.replaceChild(new Text(newContent), el);
else if (typeof newContent === 'function') {
const newText = newContent(el.wholeText);
el.parentElement.replaceChild(new Text(newText), el);
}
};
/**
* 翻译所有带选择器的内容
*
* @param el 要翻译的 html 元素
* @param allQueryContents 所有包含选择器的翻译项
*/
const translateQueryContent = function (allQueryContents) {
// 翻译所有有选择器的元素
return allQueryContents.filter(content => {
const targetElements = document.body.querySelectorAll(content.selector);
if (targetElements.length === 0)
return true;
// 执行翻译
targetElements.forEach((element, index) => {
if (!isHTMLElement(element) || isExceptElement(element))
return;
// 没有跳过检查的就从缓存里读出之前的内容进行检查
if (!content.ingnoreRepeatedCheck) {
const cacheKey = content.selector + index;
// 如果元素的内容没有发生变更,就不执行更新
const preContent = contentCache.get(cacheKey);
if (preContent !== undefined && preContent === element.innerHTML)
return;
const newContent = content.protect ?
updateProtectElement(element, content) :
updateElement(element, content);
// 更新缓存
contentCache.set(cacheKey, newContent);
return;
}
// 不然就直接进行更新
if (content.protect)
updateProtectElement(element, content);
else
updateElement(element, content);
});
return content.reuse;
});
};
/**
* 翻译所有不带 css 选择器的内容
*
* 会遍历取出 el 下的待翻译文本(Text 对象),然后和翻译项进行文本对比,若对比完全匹配则进行翻译
* **注意**,会自动移除待对比文本前后的空白字符
*
* @param el 要翻译的 html 元素
* @param allContents 所有不带选择器的翻译内容
*/
const translateNormalContent = function (el, allContents) {
if (isExceptElement(el))
return allContents;
// 取出所有待翻译元素
const needTranslateText = getContentElement(el);
// 遍历所有节点进行翻译
needTranslateText.forEach(text => {
// 这个文本有可能在之前已经被翻译了(被从其父节点上剔除),所以这里不再进行无效翻译
if (!text.parentElement)
return;
const originContent = text.wholeText;
// 找到符合的翻译内容,并保存其索引
let translationIndex;
const currentTranslation = allContents.find((content, index) => {
const matchContent = content[TRANSLATE_FROM];
const targetContent = trim(originContent);
// 使用字符串匹配
if (typeof matchContent === 'string') {
if (matchContent !== targetContent)
return false;
}
// 不然就使用正则进行匹配
else if (!matchContent.test(targetContent))
return false;
translationIndex = index;
return true;
});
// 没找到就下一个
if (!currentTranslation) {
// console.warn(`文本 ${originContent} 未翻译`)
return;
}
// 更新文本,如果没指定重用的话就将其移除
updateText(text, currentTranslation);
if (!currentTranslation.reuse)
allContents.splice(translationIndex, 1);
});
return allContents;
};
/**
* 翻译指定节点
*
* 会使用当前数据源递归翻译其子元素
* 该方法会修改 全局存储 currentPageContent 的内容(会将完成翻译的内容从 content 取出以提高性能,除非该 content 指定了 reuse)
*
* @param changedNode 发生变更的节点
*/
const translate = function (changedNode) {
const { content: allContents, queryContent: allQueryContents } = getContent();
// 有选择器的内容每次变更只会被翻译一次
const nextSearchdQueryContents = translateQueryContent(allQueryContents);
updateContent({ queryContent: nextSearchdQueryContents });
// 文本内容每个都会被执行翻译
for (const node of changedNode) {
const nextSearchContents = translateNormalContent(node, allContents);
// 把没有使用或者启用了重用的翻译内容更新回数据源
updateContent({ content: nextSearchContents });
}
};
/**
* 翻译的大致流程是:发现路由变化 > 重新加载翻译源,发现 html 元素变化 > 重新翻译内容
*
* 实现的方式有两种:
* 1. 监听 onHashChange 事件,触发时加载翻译源,再用 MutationObserver 单独监听元素变化
* 2. 使用 MutationObserver 监听元素变化,变化之前检查路由是否有变更,有的话重载翻译源
*
* 第一种方法无法保证先加载翻译源再重新翻译内容,就会出现翻译内容时还是用的之前的翻译源
* 为了解决这个问题就需要再加载翻译源后再全量翻译一次,而这些翻译内容很多都是和 MutationObserver 里的翻译是重复的,造成了性能浪费,故弃用。
* 下面则为第二种方法的实现。
*/
/**
* 设置该插件所需的回调
*
* @param callbacks 要触发的回调
*/
function listener (callbacks) {
const observer = new MutationObserver(getMutationCallback(callbacks));
// 启动监听
observer.observe(document.body, {
childList: true,
characterData: true,
subtree: true
});
return observer;
}
/**
* 包装回调
*
* MutationObserver 接受回调的入参不是单纯的 html 元素数组
* 这里将其格式化后再执行业务回调
*
* @param callback 要触发的实际回调
*/
const getMutationCallback = function ({ onHashChange, onElementChange }) {
return function (mutationsList) {
// 获取发生变更的节点
const changedNodes = [].concat(...mutationsList.map(mutation => {
if (isExceptElement(mutation.target))
return [];
if (mutation.type === 'childList') {
if (mutation.addedNodes.length > 0)
return [...mutation.addedNodes];
}
// 是节点内容变更的话就直接返回变更的节点
else if (mutation.type === 'characterData') {
return [mutation.target];
}
return [];
}));
// 如果没有发生变化的节点,就不需要翻译
if (changedNodes.length <= 0)
return;
// 翻译前检查下 hash 有没有变
const { hash } = getContent();
const newHash = getNoQueryHash(document.location.hash);
// hash 变了,重新加载翻译源然后再更新
if (hash !== newHash) {
onHashChange(document.location.hash);
updateContent({ hash: newHash });
}
// 触发回调
onElementChange(changedNodes);
};
};
/**
* 中间横排的信息一览
*/
const OVERVIEW_HEADER = {
'Control<br>points': '控制点数',
'Energy<br>harvested': '能量采集',
'Energy<br>on construct': '能量 - 建筑消耗',
'Energy<br>on creeps': '能量 - 孵化消耗',
'Creeps<br>produced': 'creep 孵化',
'Creeps<br>lost': 'creep 损失',
'Power<br>processed': 'power 处理'
};
/**
* 图表右上角的下拉框选项
*/
const GRAPH_SELECT_LIST = {
'Power processed': 'power 处理',
'Control points': '控制点数',
'Energy harvested': '能量采集',
'Energy spent on construction': '能量 - 建筑消耗',
'Energy spent on creeps': '能量 - 孵化消耗',
'Creeps produced': 'creep 孵化',
'Creeps lost': 'creep 损失'
};
/**
* 获取翻译总览数据统计
*/
const getOverviewHeaderContent = function () {
return {
'selector': '.profile-stat-title',
'zh-CN': translateMultiple(OVERVIEW_HEADER),
'reuse': true
};
};
const content = {
hashs: ['#!/overview'],
content: [
{ 'en-US': 'Overview', 'zh-CN': '总览' },
{ 'en-US': 'Global Control Level', 'zh-CN': '全局控制等级' },
{ 'en-US': 'Global Power Level', 'zh-CN': '全局超能等级' },
{ 'en-US': 'Manage Power Creeps', 'zh-CN': '管理 power creep' },
{ 'en-US': 'Stats Period', 'zh-CN': '统计时长', 'reuse': true },
{ 'en-US': /Graph(:|)/, 'zh-CN': '图表', 'reuse': true },
{ 'en-US': 'Owner:', 'zh-CN': '所有者:' },
{ 'en-US': 'View leaderboard', 'zh-CN': '查看排行榜', 'reuse': true },
getOverviewHeaderContent(),
// 翻译下拉框当前选中值
{
'selector': 'button > span.toggle-text.ng-scope > span',
'zh-CN': translateMultiple(GRAPH_SELECT_LIST),
'reuse': true
},
// 翻译下拉框选项
{
'selector': 'a.ng-binding.ng-scope',
'zh-CN': translateMultiple(GRAPH_SELECT_LIST),
'reuse': true
},
// 点开房间后的图表
{
'selector': 'div.graph-item label',
'zh-CN': translateMultiple(GRAPH_SELECT_LIST),
'reuse': true
}
]
};
const content$1 = {
hashs: ['#!/'],
content: [
// 阻止翻译右上角的 CPU 及内存使用量
dontTranslate('.cpu > .sysbar-title > strong'),
dontTranslate('.mem > div.sysbar-title > strong'),
{ 'en-US': 'Persistent world:', 'zh-CN': '永恒世界:', 'reuse': true },
{ 'en-US': 'Overview', 'zh-CN': '总览', 'reuse': true },
{ 'en-US': 'World', 'zh-CN': '世界', 'reuse': true },
{ 'en-US': 'Market', 'zh-CN': '市场', 'reuse': true },
{ 'en-US': 'Inventory', 'zh-CN': '库存', 'reuse': true },
{ 'en-US': 'Documentation', 'zh-CN': '文档', 'reuse': true },
{ 'en-US': 'Training', 'zh-CN': '练习', 'reuse': true },
{ 'en-US': 'Public Test Realm', 'zh-CN': '公共测试服务器', 'reuse': true },
{ 'en-US': 'Messages', 'zh-CN': '消息', 'reuse': true },
{ 'en-US': 'Report a problem', 'zh-CN': '问题上报', 'reuse': true },
{ 'en-US': 'Blog', 'zh-CN': '博客', 'reuse': true },
{ 'en-US': 'Forum', 'zh-CN': '论坛', 'reuse': true },
{ 'en-US': 'Terms of Service', 'zh-CN': '服务条款', 'reuse': true },
{ 'en-US': 'Privacy policy', 'zh-CN': '隐私政策', 'reuse': true },
{ 'en-US': 'Respawn', 'zh-CN': '重生', 'reuse': true },
{ 'en-US': 'View profile', 'zh-CN': '查看资料', 'reuse': true },
{ 'en-US': 'Manage account', 'zh-CN': '账户管理', 'reuse': true },
{ 'en-US': 'Sign out', 'zh-CN': '登出', 'reuse': true },
{ 'en-US': 'New update is available', 'zh-CN': '有可用的更新' },
{ 'en-US': 'RELOAD', 'zh-CN': '重新加载' },
{ 'en-US': 'Your CPU is limited', 'zh-CN': '您的 CPU 受限' },
{ 'en-US': 'Order a subscription here', 'zh-CN': '点此购买一个订阅 ' },
// 登陆弹窗
{ 'en-US': 'Sign In', 'zh-CN': '登陆', 'reuse': true },
{ 'en-US': 'E-mail or username', 'zh-CN': '邮箱或用户名', 'reuse': true },
{ 'en-US': 'Password', 'zh-CN': '密码', 'reuse': true },
{ 'en-US': 'SIGN IN', 'zh-CN': '登陆', 'reuse': true },
{ 'en-US': 'OR', 'zh-CN': '或', 'reuse': true },
{ 'en-US': 'I forgot my password', 'zh-CN': '我忘记密码了', 'reuse': true },
{ 'en-US': 'Create a new account', 'zh-CN': '创建一个新账户', 'reuse': true },
{ 'en-US': 'Account credentials are invalid', 'zh-CN': '账号验证失败', 'reuse': true },
// 右上角登陆按钮
{ 'en-US': 'Sign in', 'zh-CN': '登陆 ', 'reuse': true },
{ 'en-US': 'or register', 'zh-CN': '或注册', 'reuse': true },
{ 'en-US': 'Global Control Level has been increased!', 'zh-CN': '全局控制等级(GCL)已提升!' },
{ 'en-US': 'You can control', 'zh-CN': '您现在可以控制 ' },
{ 'en-US': /\d+ rooms/, 'zh-CN': (text) => text.replace('rooms', '个房间') },
{ 'en-US': 'now.', 'zh-CN': '了。' },
// 阻止翻译左侧边栏头部的赛季服倒计时
{
'selector': 'app-time-left',
/**
* 因为这个元素会因为未知原因销毁重建一次,导致单纯通过 dontTranslate 设置的禁止翻译被清掉了
* 所以这里加个延迟,等元素重建完成后再添加禁止翻译
*/
'zh-CN': () => setTimeout(() => {
const el = document.body.querySelector('app-time-left');
el.stopTranslateSearch = true;
}, 1000)
},
// 重生确认框
{
'en-US': 'All your buildings and creeps will become unowned so that you\n can reset your spawn in any vacant room on the map.',
'zh-CN': '您将失去所有的建筑和 creep,然后您就可以在地图上的任意无主房间重新放置 spawn。',
'reuse': true
},
{ 'en-US': 'Learn more', 'zh-CN': '了解更多', 'reuse': true },
{ 'en-US': 'Note:', 'zh-CN': '注意:', 'reuse': true },
{
'en-US': 'you will NOT be able to spawn again in the same\n room within 3 days since the initial spawn placement!',
'zh-CN': '在放置第一个 spawn 之后的三天内,您将无法再次重生在相同房间中。',
'reuse': true
},
{ 'en-US': 'Cancel', 'zh-CN': '取消', 'reuse': true }
]
};
const content$2 = {
hashs: ['#!/sim'],
content: [
{ 'en-US': 'Simulation mode', 'zh-CN': '模拟模式' },
{
'en-US': 'In this mode your script runs not on the server, but locally on your machine, so that you can pause and debug it.',
'zh-CN': '该模式下,您的代码将运行在本地机器而不是服务器上。因此,您可以暂停并对代码进行调试。'
},
{ 'en-US': 'Tutorial', 'zh-CN': '教程', 'reuse': true },
{
'en-US': 'Learn basic game concepts step by step.',
'zh-CN': '逐步了解游戏中的基本概念。'
},
{ 'en-US': 'Training', 'zh-CN': '练习' },
// 练习的文本介绍里有个换行,很气
{
'selector': 'div:nth-child(4) > a > section',
'zh-CN': '在一个预定义布局的虚拟房间中实践您的代码。',
'reuse': true
},
{ 'en-US': 'Custom', 'zh-CN': '自定义' },
{
'en-US': 'Modify the landscape, create any objects, and test your scripts playing for two virtual players at once.',
'zh-CN': '修改地形、创建任何对象并同时操控两个虚拟玩家来测试您的代码。'
},
{
'en-US': 'Your script will be saved, but your simulation progress will be lost! Are you sure?',
'zh-CN': '您的代码将会保存,但是您的模拟器进度将会丢失!确定要退出么?',
'reuse': true
},
{ 'en-US': 'Cancel', 'zh-CN': '取消', 'reuse': true },
{ 'en-US': 'OK', 'zh-CN': '确定', 'reuse': true }
]
};
const TIP_CONTENT = {
'Inscreasing the <code>reusePath</code> option in the <code>Creep.moveTo</code> method helps saving CPU.': '提高 <code>Creep.moveTo</code> 方法中的 <code>reusePath</code> 参数有助于节省 CPU。',
'Set up a grunt task to write scripts on your local machine and commit them to Screeps.': '设置一个 grunt(或者 rollup)任务来在本地编辑你的代码并将其提交到 Screep。',
'Each game action has a constant cost of 0.2 CPU.': '每个会影响游戏世界的动作都有 0.2 CPU 的固定成本。',
'Towers can aim at any object in a room even through walls and obstacles.': 'tower 可以透过墙壁和障碍物瞄准同房间中的任何对象。',
'Power banks appear only in neutral rooms that divide living sectors on the map.': 'power bank 仅出现在过道房间中,过道房是指分隔不同区块的空旷中立房间。',
'Modular architecture of a script will allow easy testing of individual functions in the simulator.': '脚本的模块化架构使得你可以在模拟器中轻松测试单个函数。',
'Test various game scenarios in the simulator in order to be prepared for surprises.': '在模拟器中测试各种游戏场景,以应对随时可能发生的意外。',
'Sources in neutral rooms have reduced capacity. Reserve or claim the room to restore it to full capacity.': '中立房间矿的能量矿(Source)上限只有1500。预订(reserve)或占领(claim)房间可以使其恢复到最大容量。',
'To save your CPU, use less creeps of a larger size.': '生成数量更少、身体部件更多的 creep 来节省你的 CPU。',
'Spawn extensions capacity increases on room levels 7 and 8.': 'RCL7 和 RCL8 将提升 extension 的容量。',
'Use towers to set up automatic defense of your room.': '使用 tower 来建立你房间的自动防御。',
'If CPU limit raises, your script will execute only partially.': '如果运算量超过 CPU 限制,未执行的脚本将会被强行终止。',
'Walking over swamps is 5 times slower compared to plain land.': '在沼泽上行走比平原慢 5 倍。',
'Use loop architecture to save CPU on the logic you do not have to run each tick.': '可以把不需要每个 tick 都运行的逻辑放在 loop 之外执行。',
'A tower’s effectiveness depends on the distance to the target.': 'tower 的工作效率取决于该 tower 到目标的距离。',
'You can create any objects in the simulator to test your script.': '你可以在模拟器中创建任何对象来测试脚本。',
'Unless you use up your CPU limit each tick, it is stored for future use.': '除非你每 tick 都用光了你的 CPU,不然没有用掉的部分会被存起来以备后续使用。',
'Your CPU Limit depends on your Global Control Level.': '你的 CPU 上线取决于您的全局控制级别(GCL)。',
'You can use more CPU than your CPU limit allows in short bursts.': '你的 CPU 使用量可以在短时间内使用超过你的 CPU 上限。(“短时间”取决于 cpu 桶中的余额)',
'Energy in a storage can not be used to spawn creeps. Transfer it to a spawn or extensions instead.': 'storage 里储存的能量不能直接用来孵化 creep,要先将能量转移到一个 spawn 或 extension 中。',
'The more body parts of one type a creep has, the greater its productivity.': '一个 creep 的身体部件越多,其效率也就越高。',
'More spawns in a room allows building more creeps at a time.': '一个房间中存在的 spawn 越多,能同时孵化的 creep 也就越多。',
'The more spawn extensions in a room, the more energy you can spend on building one creep.': '一个房间中的 spawn 和 extension 越多,可以用来孵化单个 creep 的能量也就越多。',
'You can address from your script only those rooms that contain your creeps or structures.': '只有房间中存在你的 creep 或者建筑时,你的代码才可以访问到它。',
'Ramparts can be built not just on empty squares but on existing structures too.': 'Rampart 不仅可以在空旷的地块上建造,还可以建造在已有的建筑上。',
'Ramparts and walls initially have 1 hit point. Repair them after construction.': 'rampart 和 wall 最初仅有 1 点生命值(hit),记得在建筑好后及时进行维修(repair)。',
'It is too costly and senseless to maintain an army of military creeps in the peacetime.': '在和平时期维持一支由战斗 creep 组成的军队代价太高且毫无意义。',
'Links can pass energy to other links at any point inside the same room.': 'link 可以将能量传递到同一房间内任何位置的其他 link。',
'A good way to save CPU is caching often-used paths.': '缓存常用路径是节省 CPU 的好方法。',
'While not destroyed, a rampart protects a creep or building on its square from any type of attack.': '只要一个 rampart 没有被摧毁,它就可以保护同地块上的 creep 或者建筑免受任何形式的攻击。',
'The game is fully recorded, so you can see replay of any room for the past several days.': '游戏已经被完整录制,所以你可以随时回放过去几天发生的事情。',
'The more small objects in the Memory, the more CPU spent on its parsing.': 'Memory 中的对象越简单,解析它所花费的 CPU 也就越少。',
'Use try/catch blocks in right places to avoid a complete halt of your script due to errors.': '在适当的位置使用 try/catch 代码块,以避免由异常导致的脚本崩溃。',
'Respawning in a chosen room would automatically destroy all structures except walls and roads.': '在选定的房间重生会自动摧毁房间内除墙和道路以外的所有建筑。',
'Creeps can miss each other if they walk towards each other simultaneously or follow step by step.': '两个相邻的 creep 可以无视彼此的存在进行面对面移动或者紧随移动。',
'If you want to play from scratch, you can always Respawn in a new room.': '如果你想从头开始玩,你可以随时在一个新房间里重生。',
'You can output HTML content to the console, like links to rooms.': '你可以将 HTML 内容输出到控制台,例如一个跳转到指定房间的超链接。',
'You can have as many rooms under your control as your Global Control Level.': '你可以控制的房间数与全局控制等级(GCL)一样多。',
'You can apply <code>transfer</code> and <code>heal</code> to another player’s creep, and <code>transfer,</code> <code>build</code> and <code>repair</code> to others’ structures.': '你可以 <code>transfer</code> 和 <code>heal</code> 另一个玩家的 creep,以及 <code>transfer</code>,<code>build</code> 和 <code>repair</code> 其他玩家的建筑。',
'<code>require</code> spends CPU depending on the size and complexity of the module loaded.': '<code>require</code> 所花费的 CPU 取决于要加载模块的大小及复杂度。',
'Spawn extensions do not have to be placed near spawns, their range is the whole room.': 'extension 不用放在 spawn 的边上,它们的有效范围是整个房间。',
'You can speed up downgrading of hostile room controller by using <code>Creep.attackController</code> on it.': '你可以通过使用 <code>Creep.attackController</code> 方法来加速敌对房间控制器的降级。',
'To output an object content into the console, use <code>JSON.stringify</code>.': '要将对象内容输出到控制台,请使用 <code>JSON.stringify</code>。',
'Build roads to save on <code>MOVE</code> body parts of your creeps.': '建造道路可以让你的 creep 使用更少的 <code>MOVE</code> 部件。',
'Always try to control as many rooms as your GCL allows. It will allow your colony to develop at the maximum speed.': '始终尝试控制 GCL 所允许的房间数量,这将可以使你的殖民地以最大的速率发展。',
'A resource abandoned on the ground eventually vanishes.': '丢弃在地面上的资源最终将会消失。',
'A creep can execute some commands simultaneously in one tick, for example <code>move</code>+<code>build</code>+<code>dropEnergy</code>.': '一个 creep 可以在同 tick 内同时执行多个不冲突命令,例如 <code>move</code>+<code>build</code>+<code>dropEnergy</code>。',
'Walls, roads, and containers don’t belong to any player, so they should be searched with the help of <code>FIND_STRUCTURES</code>, not <code>FIND_MY_STRUCTURES</code>.': 'wall,road 和 container 不属于任何玩家,所以搜索它们需要使用 <code>FIND_STRUCTURES</code> 而不是 <code>FIND_MY_STRUCTURES</code> 常量。',
'The <code>RANGED_ATTACK</code> body part is 3 times weaker than <code>ATTACK</code> and 2 times costlier at that.': '<code>RANGED_ATTACK</code> 身体部件的相对伤害是 <code>ATTACK</code> 部件的 1/3,但是其造价却是 <code>ATTACK</code> 的两倍。',
'Use <code>Room.energyAvailable</code> and <code>Room.energyCapacityAvailable</code> to determine how much energy all the spawns and extensions in the room contain.': '使用 <code>Room.energyAvailable</code> 和 <code>Room.energyCapacityAvailable</code> 来确定房间中所有 spawn 和 extensions 包含多少能量及能量上限是多少。',
'Observers allow to get the <code>Room</code> object for the rooms that have no objects of yours.': 'Observer 允许获取那些没有你单位存在的 <code>Room</code> 对象。',
'To control a room continuously, you need to upgrade your controller from time to time.': '想要持续控制一个房间,你需要经常的升级(upgrade)你的房间控制器(controller)。',
'The <code>Game.notify</code> function automatically groups identical messages using the specified interval.': '<code>Game.notify</code> 方法将把信息按照指定的时间间隔分组并发送。',
'Dead body parts have weight and generate fatigue as well.': '一个坏掉的身体部件也会产生疲劳。',
'Use branches to test and debug your temporary code and also do backups.': '使用分支(branch)来测试和调试您的临时代码,并记得时刻进行备份。',
'There is a keyword <code>debugger</code> in the simulator that stops your script in the browser.': '模拟器中有一个关键字 <code>debugger</code>,可以用于在浏览器中暂停脚本。',
'Roads wear out as they are used, so don’t forget to repair them.': '道路(road)在使用中会逐渐磨损,因此请不要忘记对其进行修复(repair)。',
'You can build and repair roads and containers in any rooms, even neutral ones.': '你可以在任何房间,哪怕是是中立房间中建造和维修 road 和 container。',
'To prevent other players from seizing a neutral room you want, use <code>Creep.reserveController</code>.': '使用 <code>Creep.reserveController</code> 可以防止其他玩家占领你想要的中立房间。',
'Creeps cannot move faster than 1 square per tick.': 'creep 的速度上限是 1 格/秒',
'Send emails to yourself with the function <code>Game.notify</code> to be aware of everything happening in the game.': '使用 <code>Game.notify</code> 方法向自己发送 email 来了解游戏中发生的一切。',
'The <code>console.log</code> function of the simulator displays a live expandable object in the browser console.': '<code>console.log</code> 方法(在模拟器中)将在浏览器的控制台中同步显示可展开的 object 对象。',
'Every creep dies after 1500 ticks, however you can prolong its life using the <code>Spawn.renewCreep</code> method.': '每个 creep 都会在 1500 tick 后死亡,然而你通过对其调用 <code>Spawn.renewCreep</code> 方法来延长它们的生命。',
'The creep memory is saved upon death, so clear <code>Memory.creeps.*</code> to prevent overflowing.': 'creep 死亡后其内存依旧存在,所以请清除 <code>Memory.creeps.*</code> 以避免内存溢出。',
'A creep with an <code>ATTACK</code> part automatically strikes back at every attacker by <code>ATTACK</code>.': '一个带有 <code>ATTACK</code> 身体部件的 creep 将会对敌方 <code>ATTACK</code> 进行自动反击。',
'A spawn automatically replenishes itself with power until the energy in the room reaches 300 units.': '当房间中用于孵化的能量小于 300 时,spawn 将会自动开始恢复能量,直到其能量等于 300 点。',
'Leaderboards reset to zero each month, while your game process continues.': '排行榜每个月都会进行重置,你的游戏进度并不会受到影响。',
'Use links to save on creep building and CPU.': '使用 link 来节省要孵化的 creep 以及 CPU',
'Use storage to not lose surplus of mined resources.': '使用 storage 来存储开采出来的过量资源。'
};
const tips = [
{ 'en-US': 'Do you want to turn off tips of the day?', 'zh-CN': '你想要关闭每日 TIP 么?', 'reuse': true },
{ 'en-US': 'Tip of the day:', 'zh-CN': '每日 TIP:' },
{ 'en-US': 'Don\'t show tips', 'zh-CN': '不再显示' },
{ 'en-US': 'Next tip', 'zh-CN': '下个 tip' },
{
'selector': '.tutorial-content > section > p > span',
'zh-CN': translateMultiple(TIP_CONTENT),
'reuse': true
},
{
// 后面这个奇葩的"3个房间"是因为在 sidebar 中会有一个 /\d+ rooms/ 正则先将其翻译为中文,所以这里需要调整一下
'en-US': /You cannot have more than 3 (rooms|个房间) in the Novice Area./,
'zh-CN': '在新手区(Novice Area)中你最多可以控制 3 个房间。',
'reuse': true
}
];
/**
* 添加每日提示
* 因为每日提示内容较多,并且不是每次都能看到,所以这里做成动态引入以提高性能
*/
const getTips = function () {
const tipTipOfTheDay = localStorage.getItem('tipTipOfTheDay');
// 已经禁用了每日提示
if (Number(tipTipOfTheDay) === -1)
return [];
// 如果还没看过每日提示,或者提示显示日期已经过了一天了,就添加每日提示内容
if (!tipTipOfTheDay || Number(tipTipOfTheDay) + (24 * 60 * 60 * 1000) < new Date().getTime()) {
return tips;
}
return [];
};
const CONSTRUCT_NOTICE = {
'Choose location': '选择位置',
'Place your spawn': '放置您的 Spawn'
};
const TOOLTIP_LABEL = {
'World': '世界',
'Room overview': '房间总览',
'Replay room history': '回放房间录像',
'View / Pan': '查看 / 拖动',
'Create Flag': '创建旗帜',
'Construct': '建筑',
'Customize': '自定义房间设置',
'Pause tracking': '停止追踪',
'Clear': '清空日志',
'Main memory': '主内存',
'Segments': '分段内存',
'Hide side panel': '隐藏侧边栏',
'Display options': '显示设置',
'Place spawn': '放置 spawn'
};
const content$3 = {
hashs: ['#!/room', '#!/sim/custom', '#!/sim/survival', '#!/sim/tutorial/', '#!/history'],
content: [
// 禁止翻译代码、控制台、内存字段
dontTranslate('.ace_editor'),
dontTranslate('.console-messages-list'),
dontTranslate('.memory-content'),
dontTranslate('.memory-segment-content'),
dontTranslate('form.console-input'),
{
'selector': 'div.tooltip.ng-scope.ng-isolate-scope > div.tooltip-inner.ng-binding',
'zh-CN': (el) => {
const newContent = TOOLTIP_LABEL[el.innerHTML];
if (newContent) {
el.innerHTML = newContent;
// 某些中文的 tooltip 会每个字都换行,非常难看,所以指定一个宽度将其撑开
el.style.minWidth = `${18 * newContent.length}px`;
}
},
'reuse': true
},
// 下方 Script 面板
{ 'en-US': 'Script', 'zh-CN': '脚本' },
{ 'en-US': 'Branch:', 'zh-CN': '分支:', 'reuse': true },
{ 'en-US': 'Modules', 'zh-CN': '模块', 'reuse': true },
{ 'en-US': 'Choose active branch:', 'zh-CN': '选择活动分支', 'reuse': true },
{ 'en-US': 'Add normal module', 'zh-CN': '添加普通模块', 'reuse': true },
{ 'en-US': 'Add binary module', 'zh-CN': '添加二进制模块', 'reuse': true },
{
'selector': 'section > section > div:nth-child(2) > div.modules-list > form > input',
'zh-CN': (el) => {
el.placeholder = '输入新模块名称...';
},
'reuse': true
},
// // 下方 Console 面板
{ 'en-US': 'Console', 'zh-CN': '控制台' },
// // 下方 Memory 面板
{ 'en-US': 'Memory', 'zh-CN': '内存' },
// 为了放置内存字段被错误翻译,内存面板被整个禁止翻译了,所以这个也就用不到了
// {
// 'selector': 'div.tab-pane > .ng-scope > section > div:nth-child(2) > div > form > input',
// 'zh-CN': (el: HTMLInputElement) => {
// el.placeholder = '添加新的内存监视路径,例如:creeps.John'
// }
// },
{ 'en-US': 'SEGMENT #:', 'zh-CN': '片段 #:', 'reuse': true },
{ 'en-US': 'Sign:', 'zh-CN': '签名:', 'reuse': true },
// 右侧 panel 名
// 装扮面板
{ 'en-US': 'Decorations', 'zh-CN': '装饰' },
{ 'en-US': 'View in inventory', 'zh-CN': '在库存中查看' },
{ 'en-US': 'World Map', 'zh-CN': '世界地图' },
// 入侵者面板
{ 'en-US': 'Invasion', 'zh-CN': '入侵' },
{ 'en-US': 'Type', 'zh-CN': '类型' },
{ 'en-US': 'Melee', 'zh-CN': '近战' },
{ 'en-US': 'Ranged', 'zh-CN': '远程' },
{ 'en-US': 'Healer', 'zh-CN': '治疗' },
{ 'en-US': 'Size', 'zh-CN': '大小' },
{ 'en-US': 'Small', 'zh-CN': '小型' },
{ 'en-US': 'Big', 'zh-CN': '大型' },
{ 'en-US': 'Boosted', 'zh-CN': '强化' },
{ 'en-US': 'Create an invader', 'zh-CN': '创造入侵者' },
// 坐标面板
{ 'en-US': 'Cursor', 'zh-CN': '坐标' },
{ 'en-US': 'Terrain:', 'zh-CN': '地形' },
{
'selector': '.cursor.ng-isolate-scope > div > div > div > span',
'zh-CN': translateMultiple({
'plain': '平原(plain)',
'swamp': '沼泽(swamp)',
'wall': '墙壁(wall)'
}),
'protect': true,
'reuse': true
},
// RoomObject 面板
{ 'en-US': 'Position:', 'zh-CN': '位置 position:', 'reuse': true },
{ 'en-US': 'Hits:', 'zh-CN': '生命值 hits:', 'reuse': true },
{ 'en-US': 'Owner:', 'zh-CN': '所有者 owner:', 'reuse': true },
{ 'en-US': 'Energy:', 'zh-CN': '能量 energy:', 'reuse': true },
{ 'en-US': 'Cooldown:', 'zh-CN': '冷却 cooldown:', 'reuse': true },
{ 'en-US': 'Decay in:', 'zh-CN': '老化 decay:', 'reuse': true },
{ 'en-US': 'Public:', 'zh-CN': '开放 public:', 'reuse': true },
{ 'en-US': 'Name:', 'zh-CN': '名称 name:', 'reuse': true },
{ 'en-US': 'Fatigue:', 'zh-CN': '疲劳 fatigue:', 'reuse': true },
{ 'en-US': 'Time to live:', 'zh-CN': '剩余存活时间:', 'reuse': true },
{ 'en-US': 'Make public', 'zh-CN': '设为开放', 'reuse': true },
{ 'en-US': 'Make non-public', 'zh-CN': '设为非开放', 'reuse': true },
{ 'en-US': 'Notify me when attacked', 'zh-CN': '被攻击时通知我', 'reuse': true },
{ 'en-US': 'Destroy this structure', 'zh-CN': '摧毁该建筑', 'reuse': true },
{ 'en-US': 'Click again to confirm', 'zh-CN': '再次点击以确认', 'reuse': true },
{ 'en-US': 'Mineral:', 'zh-CN': '矿藏 mineral:', 'reuse': true },
{ 'en-US': 'Density:', 'zh-CN': '丰度 density:', 'reuse': true },
{ 'en-US': 'Amount:', 'zh-CN': '余量 amount:', 'reuse': true },
{ 'en-US': 'Regeneration in:', 'zh-CN': '重新生成于:', 'reuse': true },
{ 'en-US': 'Learn more', 'zh-CN': '了解更多', 'reuse': true },
{ 'en-US': 'Build an extractor here to mine this mineral deposit.', 'zh-CN': '在此处建筑一个 extractor 以采集该矿藏。', 'reuse': true },
{ 'en-US': 'Amount:', 'zh-CN': '余量 amount:', 'reuse': true },
{ 'en-US': 'Level:', 'zh-CN': '等级 level:', 'reuse': true },
{ 'en-US': 'Safe modes available:', 'zh-CN': '剩余安全模式:', 'reuse': true },
{ 'en-US': 'Downgrade in:', 'zh-CN': '降级时间:', 'reuse': true },
{ 'en-US': 'Power enabled:', 'zh-CN': '是否启用 Power:', 'reuse': true },
{ 'en-US': 'Activate safe mode', 'zh-CN': '激活安全模式', 'reuse': true },
{ 'en-US': 'This action will consume 1 available safe mode activation. Proceed?', 'zh-CN': '这将会消耗掉一次安全模式激活次数,确定继续?', 'reuse': true },
{ 'en-US': 'Unclaim', 'zh-CN': '取消占领', 'reuse': true },
// 建筑面板
// 建筑
{ 'en-US': 'Construct', 'zh-CN': '建筑', 'reuse': true },
// 建筑过多弹窗
{
'en-US': 'You have too many construction sites. The maximum number of construction sites per player is 100.',
'zh-CN': '您创建的 construction site 过多。每个玩家能够创建的 construction site 上限为 100。',
'reuse': true
},
// 下方提示
{
'selector': 'g > text',
'zh-CN': translateMultiple(CONSTRUCT_NOTICE),
'reuse': true
},
// 建筑状态
// 无法更新可建筑数量,暂时禁用
// {
// 'selector': 'div > div > div > button > .ng-scope > div',
// 'zh-CN': (el: HTMLElement) => {
// el.innerHTML = el.innerHTML.replace('Available:', '可建造数:')
// el.innerHTML = el.innerHTML.replace('required', '')
// el.innerHTML = el.innerHTML.replace('RCL ', '要求RCL')
// el.innerHTML = el.innerHTML.replace('Available', '可建造')
// el.innerHTML = el.innerHTML.replace('No controller', '控制器无效')
// },
// 'reuse': true
// },
// Spawn 建造弹窗
{ 'en-US': 'Create', 'zh-CN': '建造', 'reuse': true },
{ 'en-US': 'Enter name:', 'zh-CN': '输入名称', 'reuse': true },
{ 'en-US': 'Cancel', 'zh-CN': '取消', 'reuse': true },
{ 'en-US': 'OK', 'zh-CN': '确认', 'reuse': true },
// 建筑描述
{ 'en-US': 'Contains additional energy which can be used by spawns for spawning bigger creeps.', 'zh-CN': '为 Spawn 提供生产更大体型 creep 所需要的储能空间。', 'reuse': true },
{ 'en-US': 'Decreases movement cost. Decays over time and requires repair.', 'zh-CN': '降低移动的消耗。会随着时间推移而老化并需要维护。', 'reuse': true },
{ 'en-US': 'Blocks movement of all creeps. Requires repair after construction.', 'zh-CN': '能够阻挡所有 creep。建造之后需要维护。', 'reuse': true },
{
'en-US': 'Defends creeps and structures on the same tile and blocks enemy movement. Decays over time and requires repair.',
'zh-CN': '保护位于同一位置的 creep 及建筑,能够阻挡敌人。会随着时间推移而老化并需要维护。',
'reuse': true
},
{
'en-US': 'Remotely attacks or heals any creep in a room, or repairs a structure.',
'zh-CN': '能够对同房间的任意 creep 进行远距离攻击或治疗,也可对建筑进行维护。',
'reuse': true
},
{ 'en-US': 'Stores up to 2,000 resource units. Decays over time and requires repair.', 'zh-CN': '能够存储 2,000 点资源。会随着时间推移而老化并需要维护。', 'reuse': true },
{ 'en-US': 'Stores up to 1,000,000 resource units.', 'zh-CN': '能够存储 1,000,000 点资源。', 'reuse': true },
{ 'en-US': 'Remotely transfers energy to another Link in the same room.', 'zh-CN': '能够向同房间的 Link 远距离传送能量。', 'reuse': true },
{ 'en-US': 'Allows to mine a mineral deposit.', 'zh-CN': '允许玩家采集矿物。', 'reuse': true },
{ 'en-US': 'Produces mineral compounds and boosts creeps.', 'zh-CN': '能够制造矿物化合物并强化 creep。', 'reuse': true },
{ 'en-US': 'Sends any resources to a\u00A0Terminal in another room.', 'zh-CN': '能够向另一房间的 Terminal 发送任意资源。', 'reuse': true },
{ 'en-US': 'Produces trade commodities.', 'zh-CN': '能够生产可交易商品。', 'reuse': true },
{ 'en-US': 'Spawns creeps using energy contained in the room spawns and extensions.', 'zh-CN': '使用房间内 Spawn 与 Extension 储备的能量生产 creep。', 'reuse': true },
{ 'en-US': 'Provides visibility into a distant room from your script.', 'zh-CN': '能够使您的脚本获取远处一房间的视野。', 'reuse': true },
{ 'en-US': 'Spawns power creeps with special unique powers.', 'zh-CN': '能够生产拥有特殊技能的超能 creep。', 'reuse': true },
{ 'en-US': 'Launches a nuke to a distant room dealing huge damage to the landing area.', 'zh-CN': '能够向远处一房间发射核弹,对命中区域造成巨大伤害。', 'reuse': true },
// 右侧面板相关提示
{
'selector': 'a.help.ng-scope',
'zh-CN': (el) => {
el.setAttribute('title', '该 controller 在降级时间达到最大之前无法升级(点击了解详情)');
},
'reuse': true
},
{
'selector': 'div.damaged.ng-binding.ng-scope > a',
'zh-CN': (el) => {
el.setAttribute('title', '通过升级 controller 避免降级(点击了解详情)');
},
'reuse': true
},
// 建筑工地面板
{ 'en-US': 'Construction Site', 'zh-CN': '建筑工地', 'reuse': true },
{ 'en-US': 'Structure:', 'zh-CN': '建筑(structure):', 'reuse': true },
{ 'en-US': 'Progress:', 'zh-CN': '进度(progress):', 'reuse': true },
{ 'en-US': 'Remove construction site', 'zh-CN': '移除建筑工地', 'reuse': true },
// creep 面板
{ 'en-US': 'Suicide', 'zh-CN': '自杀 suicide' },
{ 'en-US': 'View memory', 'zh-CN': '查看 memory' },
{ 'en-US': 'Body', 'zh-CN': '部件' },
// powercreep
{ 'en-US': 'Class:', 'zh-CN': '种类:', 'reuse': true },
// 房间显示设置
{ 'en-US': 'Show my names', 'zh-CN': '显示己方名称', 'reuse': true },
{ 'en-US': 'Show hostile names', 'zh-CN': '显示敌方名称', 'reuse': true },
{ 'en-US': 'Show flags', 'zh-CN': '显示旗帜(flag)', 'reuse': true },
{ 'en-US': 'Show flags names', 'zh-CN': '显示旗帜(flag)名称', 'reuse': true },
{ 'en-US': 'Show creeps speech', 'zh-CN': '显示 creep 的对话气泡', 'reuse': true },
{ 'en-US': 'Show visuals', 'zh-CN': '显示房间视觉效果(RoomVisual)', 'reuse': true },
{ 'en-US': 'Lighting:', 'zh-CN': '单位提供光照:', 'reuse': true },
{ 'en-US': 'Swamp texture:', 'zh-CN': '沼泽纹理:', 'reuse': true },
{ 'en-US': 'Hardware acceleration (WebGL)', 'zh-CN': '硬件加速(WebGL)', 'reuse': true },
{ 'en-US': 'Show metrics', 'zh-CN': '显示相关参数', 'reuse': true },
{ 'en-US': 'HD resolution:', 'zh-CN': '高清显示设置:', 'reuse': true },
{ 'en-US': 'Upscaling (performance)', 'zh-CN': 'Upscaling(性能)', 'reuse': true },
{ 'en-US': 'Native (quality)', 'zh-CN': 'Native(效果)', 'reuse': true },
{ 'en-US': 'Normal', 'zh-CN': '正常', 'reuse': true },
{ 'en-US': 'Low', 'zh-CN': '低', 'reuse': true },
{ 'en-US': 'Disabled', 'zh-CN': '关闭', 'reuse': true },
{ 'en-US': 'Animated', 'zh-CN': '动态', 'reuse': true },
{ 'en-US': 'Static', 'zh-CN': '静态', 'reuse': true },
// effect面板
{ 'en-US': 'Effects', 'zh-CN': '效果', 'reuse': true },
// {
// 'selector': 'div.effect-icon',
// 'zh-CN': (el: HTMLElement) => {
// let text = el.getAttribute('title')
// text = text.replace('Ticks remaining', '剩余时长')
// el.setAttribute('title', text)
// },
// 'reuse': true
// },
{
'en-US': 'While this structure is alive, it will send invader creeps to all rooms in this sector. It also seems there are some valuable resources inside.',
'zh-CN': '当该建筑存在时, 会在本 sector 的全部房间生成 invader creeps。其内部似乎有贵重的资源。',
'reuse': true
},
// 特殊建筑面板
// portal
{ 'en-US': 'Destination:', 'zh-CN': '目的地 destination:', 'reuse': true },
// controller
{ 'en-US': 'Reserved:', 'zh-CN': '预定:', 'reuse': true },