prts-terrarchive 0.2.0
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.
- package/GAME_ASSETS.md +26 -0
- package/LICENSE +21 -0
- package/README.en.md +274 -0
- package/README.md +423 -0
- package/THIRD_PARTY_NOTICES.md +61 -0
- package/bin/bake-rhine-details.mjs +34 -0
- package/bin/install.js +273 -0
- package/bin/pack-map-assets.mjs +110 -0
- package/bin/preview-rhine.mjs +71 -0
- package/bin/rhine-detail-baker.ts +84 -0
- package/contracts/cloud_tools_v1.schema.json +243 -0
- package/contracts/corpus_i18n_v1.schema.json +179 -0
- package/contracts/corpus_tools_v1.schema.json +1801 -0
- package/cordis.patch.yml +32 -0
- package/docs/endfield-official-i18n.md +103 -0
- package/lib/client.js +3912 -0
- package/lib/endfield-map/map.js.br +0 -0
- package/lib/endfield-map/map.js.gz +0 -0
- package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.br +0 -0
- package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.gz +0 -0
- package/lib/endfield-map/resources/map-01616f534aaaeca1.png +0 -0
- package/lib/endfield-map/resources/map-1562e0dff4be3d23.png +0 -0
- package/lib/endfield-map/resources/map-160fe040f080f6a3.png +0 -0
- package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.br +0 -0
- package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.gz +0 -0
- package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.br +0 -0
- package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.gz +0 -0
- package/lib/endfield-map/resources/map-1d30baaaef3e29cc.png +0 -0
- package/lib/endfield-map/resources/map-21a61386af79cd12.json.br +0 -0
- package/lib/endfield-map/resources/map-21a61386af79cd12.json.gz +0 -0
- package/lib/endfield-map/resources/map-24b920b8dab49b44.json.br +0 -0
- package/lib/endfield-map/resources/map-24b920b8dab49b44.json.gz +0 -0
- package/lib/endfield-map/resources/map-26caf8d71247abb7.png +0 -0
- package/lib/endfield-map/resources/map-2729942c89545e73.json.br +0 -0
- package/lib/endfield-map/resources/map-2729942c89545e73.json.gz +0 -0
- package/lib/endfield-map/resources/map-29063a9802f67014.json.br +0 -0
- package/lib/endfield-map/resources/map-29063a9802f67014.json.gz +0 -0
- package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.br +0 -0
- package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.gz +0 -0
- package/lib/endfield-map/resources/map-2f98a873da81425e.json.br +0 -0
- package/lib/endfield-map/resources/map-2f98a873da81425e.json.gz +0 -0
- package/lib/endfield-map/resources/map-3ab978ae44190f80.json.br +0 -0
- package/lib/endfield-map/resources/map-3ab978ae44190f80.json.gz +0 -0
- package/lib/endfield-map/resources/map-3b62c80d14208902.json.br +0 -0
- package/lib/endfield-map/resources/map-3b62c80d14208902.json.gz +0 -0
- package/lib/endfield-map/resources/map-3d486e887ae121ac.png +0 -0
- package/lib/endfield-map/resources/map-418dc4865608fce5.png +0 -0
- package/lib/endfield-map/resources/map-4366b84607335f76.json.br +0 -0
- package/lib/endfield-map/resources/map-4366b84607335f76.json.gz +0 -0
- package/lib/endfield-map/resources/map-4371259d3ab7aac7.png +0 -0
- package/lib/endfield-map/resources/map-48dcc0fd55e13f7f.png +0 -0
- package/lib/endfield-map/resources/map-4aabda0e41197570.json.br +0 -0
- package/lib/endfield-map/resources/map-4aabda0e41197570.json.gz +0 -0
- package/lib/endfield-map/resources/map-505f5687b287043c.json.br +0 -0
- package/lib/endfield-map/resources/map-505f5687b287043c.json.gz +0 -0
- package/lib/endfield-map/resources/map-5177e9d1d46f47e2.png +0 -0
- package/lib/endfield-map/resources/map-51baeb59b7f8c28d.png +0 -0
- package/lib/endfield-map/resources/map-549561b46e621874.png +0 -0
- package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.br +0 -0
- package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.gz +0 -0
- package/lib/endfield-map/resources/map-5a6963909423b674.png +0 -0
- package/lib/endfield-map/resources/map-5a9c533c0552d7b7.png +0 -0
- package/lib/endfield-map/resources/map-5d20ae9ffa9a02b0.png +0 -0
- package/lib/endfield-map/resources/map-626c8847393f60bf.png +0 -0
- package/lib/endfield-map/resources/map-63d43cbec3bed3b8.png +0 -0
- package/lib/endfield-map/resources/map-661b6ec56e722574.png +0 -0
- package/lib/endfield-map/resources/map-6ddd384eca190828.png +0 -0
- package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.br +0 -0
- package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.gz +0 -0
- package/lib/endfield-map/resources/map-738ebaa002c33cfc.png +0 -0
- package/lib/endfield-map/resources/map-758c13be6085ec07.json.br +0 -0
- package/lib/endfield-map/resources/map-758c13be6085ec07.json.gz +0 -0
- package/lib/endfield-map/resources/map-78929f26d6844509.png +0 -0
- package/lib/endfield-map/resources/map-7f09c142950a0f74.png +0 -0
- package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.br +0 -0
- package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.gz +0 -0
- package/lib/endfield-map/resources/map-881343926a568f27.json.br +0 -0
- package/lib/endfield-map/resources/map-881343926a568f27.json.gz +0 -0
- package/lib/endfield-map/resources/map-8d1ade3f01d952b0.png +0 -0
- package/lib/endfield-map/resources/map-8fef3495bb7ed60f.png +0 -0
- package/lib/endfield-map/resources/map-907a7ecf8a0ce352.png +0 -0
- package/lib/endfield-map/resources/map-9113d5951c4db903.json.br +0 -0
- package/lib/endfield-map/resources/map-9113d5951c4db903.json.gz +0 -0
- package/lib/endfield-map/resources/map-94ad6634436224e3.json.br +0 -0
- package/lib/endfield-map/resources/map-94ad6634436224e3.json.gz +0 -0
- package/lib/endfield-map/resources/map-99722e61da5f3e53.png +0 -0
- package/lib/endfield-map/resources/map-9be9596cc059abe3.png +0 -0
- package/lib/endfield-map/resources/map-a4db9a4763e47fbf.png +0 -0
- package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.br +0 -0
- package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.gz +0 -0
- package/lib/endfield-map/resources/map-ab35d06875ee418f.png +0 -0
- package/lib/endfield-map/resources/map-ac51bb3e7623cd8b.png +0 -0
- package/lib/endfield-map/resources/map-ac830a66296bc83a.png +0 -0
- package/lib/endfield-map/resources/map-b3fd04209718e55e.json.br +0 -0
- package/lib/endfield-map/resources/map-b3fd04209718e55e.json.gz +0 -0
- package/lib/endfield-map/resources/map-b8164f1af7c5d895.png +0 -0
- package/lib/endfield-map/resources/map-b8cde93b965384d8.json.br +0 -0
- package/lib/endfield-map/resources/map-b8cde93b965384d8.json.gz +0 -0
- package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.br +0 -0
- package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.gz +0 -0
- package/lib/endfield-map/resources/map-bfc58277d5ba649d.png +0 -0
- package/lib/endfield-map/resources/map-c013f82048c6891a.png +0 -0
- package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.br +0 -0
- package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.gz +0 -0
- package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.br +0 -0
- package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.gz +0 -0
- package/lib/endfield-map/resources/map-c607f8b4c8ad8967.png +0 -0
- package/lib/endfield-map/resources/map-c6f20276b8781326.json.br +0 -0
- package/lib/endfield-map/resources/map-c6f20276b8781326.json.gz +0 -0
- package/lib/endfield-map/resources/map-d51adf5952af1e8e.png +0 -0
- package/lib/endfield-map/resources/map-d695eface22f8b74.json.br +0 -0
- package/lib/endfield-map/resources/map-d695eface22f8b74.json.gz +0 -0
- package/lib/endfield-map/resources/map-db11d394456a7465.png +0 -0
- package/lib/endfield-map/resources/map-dcc4d218581e3dc6.png +0 -0
- package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.br +0 -0
- package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.gz +0 -0
- package/lib/endfield-map/resources/map-defe5db7dabf748a.png +0 -0
- package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.br +0 -0
- package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.gz +0 -0
- package/lib/endfield-map/resources/map-e25ee9af6e493500.png +0 -0
- package/lib/endfield-map/resources/map-e3824082cd6f747d.json.br +0 -0
- package/lib/endfield-map/resources/map-e3824082cd6f747d.json.gz +0 -0
- package/lib/endfield-map/resources/map-e694a91e630b1bd6.png +0 -0
- package/lib/endfield-map/resources/map-eaa93af79d12a87d.png +0 -0
- package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.br +0 -0
- package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.gz +0 -0
- package/lib/endfield-map/resources/map-ede421a329dc78a4.png +0 -0
- package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.br +0 -0
- package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.gz +0 -0
- package/lib/endfield-map/resources/map-fb5f4a59a181452c.png +0 -0
- package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.br +0 -0
- package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.gz +0 -0
- package/lib/rhine/assets/archive-assembly.glb +0 -0
- package/lib/rhine/assets/archive-cassette.glb +0 -0
- package/lib/rhine/fonts/MiSans-Bold.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Demibold.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Light.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Regular.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-license.pdf +0 -0
- package/lib/rhine/fonts/NOTICE.txt +6 -0
- package/lib/rhine/licenses/RhineLabUI-MIT.txt +21 -0
- package/lib/rhine/licenses/entities-BSD-2-Clause.txt +11 -0
- package/lib/rhine/licenses/linkify-it-MIT.txt +22 -0
- package/lib/rhine/licenses/markdown-it-MIT.txt +22 -0
- package/lib/rhine/licenses/mdurl-MIT.txt +45 -0
- package/lib/rhine/licenses/punycode.js-MIT.txt +20 -0
- package/lib/rhine/licenses/rolling-number-MIT.txt +21 -0
- package/lib/rhine/licenses/three-MIT.txt +21 -0
- package/lib/rhine/licenses/uc.micro-MIT.txt +20 -0
- package/lib/rhine/rhine.css +1 -0
- package/lib/rhine/rhine.js +5106 -0
- package/lib/skins/common.css +31 -0
- package/lib/skins/endfield-aic.css +141 -0
- package/lib/skins/prts-agent.css +136 -0
- package/lib/skins/rhine-lab.css +70 -0
- package/package.json +112 -0
- package/presets/definition.js +26 -0
- package/presets/prts/agent.cordis.yml +25 -0
- package/presets/prts/preset.yml +3 -0
- package/presets/register.js +86 -0
- package/resources/entity-routing.json +21 -0
- package/skills/prts-investigation/SKILL.md +121 -0
- package/skills/prts-retrieval/SKILL.md +35 -0
- package/skills/prts-retrieval/references/cloud-tools.md +13 -0
- package/skills/prts-retrieval/references/diagnostics.md +15 -0
- package/skills/prts-retrieval/references/local-tools.md +24 -0
- package/skills/prts-retrieval/references/module-arknights.md +23 -0
- package/skills/prts-retrieval/references/module-dual.md +15 -0
- package/skills/prts-retrieval/references/module-endfield.md +21 -0
- package/skills/prts-retrieval/references/retrieval-process.md +44 -0
- package/skills/prts-retrieval/references/retrieval-recipes-dual.md +34 -0
- package/skills/prts-retrieval/references/retrieval-recipes-endfield.md +37 -0
- package/skills/prts-retrieval/references/retrieval-recipes.md +39 -0
- package/skills/prts-retrieval/references/tools-runtime.md +48 -0
- package/skills/prts-retrieval/references/wiki-schema.md +38 -0
- package/src/archive-presentation.js +287 -0
- package/src/cloud-projection.js +124 -0
- package/src/cloud.js +498 -0
- package/src/entity-recognizer.js +311 -0
- package/src/entity-routing.js +82 -0
- package/src/evidence-state.js +332 -0
- package/src/i18n.js +365 -0
- package/src/index.js +1261 -0
- package/src/installer.js +1362 -0
- package/src/investigation-review.js +87 -0
- package/src/investigation-store.js +579 -0
- package/src/investigation-tools.js +113 -0
- package/src/localization-format.js +23 -0
- package/src/manual-search.js +103 -0
- package/src/read.js +1097 -0
- package/src/release-compatibility.js +73 -0
- package/src/release-watch.js +40 -0
- package/src/search-projection.js +139 -0
- package/src/search.js +1522 -0
- package/src/skill.js +98 -0
- package/src/source-map.js +348 -0
- package/src/state.js +314 -0
- package/src/store.js +1569 -0
- package/src/timeline.js +386 -0
- package/src/ui.js +1353 -0
- package/src/wiki.js +111 -0
- package/tsconfig.rhine.json +15 -0
- package/ui/rhine/archive-navigation-limit.ts +31 -0
- package/ui/rhine/archive-rack.ts +83 -0
- package/ui/rhine/assets/evidence-observatory.webp +0 -0
- package/ui/rhine/board-plane-transform.ts +24 -0
- package/ui/rhine/catalogue.ts +72 -0
- package/ui/rhine/deferred-preparation.ts +146 -0
- package/ui/rhine/evidence-board-controls.css +22 -0
- package/ui/rhine/evidence-board-history.ts +73 -0
- package/ui/rhine/evidence-board-model.ts +189 -0
- package/ui/rhine/evidence-board-panel.ts +762 -0
- package/ui/rhine/evidence-board-scene.ts +548 -0
- package/ui/rhine/evidence-board.css +191 -0
- package/ui/rhine/evidence-card-art.ts +470 -0
- package/ui/rhine/evidence-card-style.ts +17 -0
- package/ui/rhine/evidence-inbox-scene.ts +43 -0
- package/ui/rhine/evidence-inbox.css +29 -0
- package/ui/rhine/evidence-inbox.ts +104 -0
- package/ui/rhine/index.ts +12 -0
- package/ui/rhine/interaction.css +20 -0
- package/ui/rhine/investigation-board-plane.ts +5 -0
- package/ui/rhine/investigation-board.css +34 -0
- package/ui/rhine/investigation-board.ts +348 -0
- package/ui/rhine/investigation-edits.ts +46 -0
- package/ui/rhine/investigation-layout.ts +29 -0
- package/ui/rhine/label-prefetch.ts +34 -0
- package/ui/rhine/looping-title.ts +40 -0
- package/ui/rhine/manual-search.css +41 -0
- package/ui/rhine/manual-search.ts +89 -0
- package/ui/rhine/original/appearance.ts +180 -0
- package/ui/rhine/original/archive-label.ts +84 -0
- package/ui/rhine/original/archive-lighting.ts +39 -0
- package/ui/rhine/original/archive-loop.ts +82 -0
- package/ui/rhine/original/archive-refill.ts +64 -0
- package/ui/rhine/original/array-detail-data.ts +2 -0
- package/ui/rhine/original/array-detail.ts +41 -0
- package/ui/rhine/original/array-interior.ts +411 -0
- package/ui/rhine/original/array-occlusion.ts +166 -0
- package/ui/rhine/original/array-shell.ts +38 -0
- package/ui/rhine/original/array-visibility.ts +309 -0
- package/ui/rhine/original/asset-url.ts +4 -0
- package/ui/rhine/original/brand.ts +12 -0
- package/ui/rhine/original/cassette-lod.ts +81 -0
- package/ui/rhine/original/data.ts +21 -0
- package/ui/rhine/original/decryption.ts +185 -0
- package/ui/rhine/original/detail-signature.ts +18 -0
- package/ui/rhine/original/glass-reveal.ts +45 -0
- package/ui/rhine/original/internal-optics.ts +70 -0
- package/ui/rhine/original/label-renderer.ts +119 -0
- package/ui/rhine/original/label-textures.ts +117 -0
- package/ui/rhine/original/model-viewer.ts +658 -0
- package/ui/rhine/original/motion.ts +116 -0
- package/ui/rhine/original/opaque-backfaces.ts +64 -0
- package/ui/rhine/original/program-preparation.ts +111 -0
- package/ui/rhine/original/quality-renderer.ts +97 -0
- package/ui/rhine/original/quality-settings.ts +111 -0
- package/ui/rhine/original/render-quality.ts +163 -0
- package/ui/rhine/original/scene.ts +2051 -0
- package/ui/rhine/original/shelf-interior.ts +142 -0
- package/ui/rhine/original/ui-transitions.ts +111 -0
- package/ui/rhine/original/viewer-camera.ts +92 -0
- package/ui/rhine/original/viewport-layout.ts +41 -0
- package/ui/rhine/original-ui.css +2334 -0
- package/ui/rhine/readability.css +46 -0
- package/ui/rhine/reading-object.ts +96 -0
- package/ui/rhine/reading-origin.ts +28 -0
- package/ui/rhine/reading-room.css +55 -0
- package/ui/rhine/report-markdown.ts +98 -0
- package/ui/rhine/scene-edge-navigation.ts +84 -0
- package/ui/rhine/scene.ts +1726 -0
- package/ui/rhine/shelf-layout.ts +38 -0
- package/ui/rhine/snapshot-update-gate.ts +22 -0
- package/ui/rhine/source-reading.ts +15 -0
- package/ui/rhine/source-selection.css +164 -0
- package/ui/rhine/temporary-performance-observers.ts +163 -0
- package/ui/rhine/temporary-performance-panel.ts +124 -0
- package/ui/rhine/temporary-performance.css +19 -0
- package/ui/rhine/temporary-performance.ts +552 -0
- package/ui/rhine/temporary-render-diagnostics.ts +105 -0
- package/ui/rhine/tool-activity.ts +69 -0
- package/ui/rhine/types.ts +219 -0
- package/ui/rhine/visible-scene-hit.ts +13 -0
- package/ui/rhine/workbench.css +818 -0
- package/ui/rhine/workbench.ts +2245 -0
- package/ui/rhine/workspace-travel.ts +32 -0
- package/vite.rhine.config.ts +19 -0
package/src/i18n.js
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/** Official localizations: exact source identities, independent of reading coverage. */
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import { assertCorpusVersion, corpusVersionSnapshot, documentGame, documentUid,
|
|
4
|
+
naturalDocumentTitle } from './store.js'
|
|
5
|
+
import { LOCALIZATION_LANGUAGES } from './localization-format.js'
|
|
6
|
+
|
|
7
|
+
const PACK = 'endfield_official_game'
|
|
8
|
+
const states = new WeakMap()
|
|
9
|
+
const fields = ['text', 'name', 'title', 'actor', 'hint']
|
|
10
|
+
const sha256 = (value) => createHash('sha256').update(value).digest('hex')
|
|
11
|
+
const normalize = (value) => String(value)
|
|
12
|
+
.replace(/<image[^>]*>[\s\S]*?<\/image>|<image\s*=\s*[^>]*>/giu, '')
|
|
13
|
+
.replace(/<br\s*\/?>/giu, '\n')
|
|
14
|
+
.replace(/<@[^>]*>|<\/>|<\/?(?:color|b|i|size)(?:=[^>]*)?>/giu, '')
|
|
15
|
+
.normalize('NFKC').toLowerCase().replace(/\s+/gu, ' ').trim()
|
|
16
|
+
const aliases = { 'zh-cn': 'CN', 'zh-hans': 'CN', en: 'EN', ja: 'JP', ko: 'KR',
|
|
17
|
+
'zh-tw': 'TC', 'zh-hant': 'TC', 'es-mx': 'MX', 'pt-br': 'BR', fr: 'FR', de: 'DE',
|
|
18
|
+
ru: 'RU', it: 'IT', id: 'ID', th: 'TH', vi: 'VN' }
|
|
19
|
+
for (const [locale, code] of Object.entries({ 'en-us': 'EN', 'ja-jp': 'JP', 'ko-kr': 'KR',
|
|
20
|
+
'fr-fr': 'FR', 'de-de': 'DE', 'ru-ru': 'RU', 'it-it': 'IT', 'id-id': 'ID', 'th-th': 'TH', 'vi-vn': 'VN' })) {
|
|
21
|
+
aliases[locale] = code
|
|
22
|
+
}
|
|
23
|
+
const fault = (code, message) => Object.assign(new Error(message), { code,
|
|
24
|
+
retryable: code === 'PACKAGE_VERSION_MISMATCH' })
|
|
25
|
+
|
|
26
|
+
export const I18N_DESCRIPTION = [
|
|
27
|
+
'查询终末地官方本地化文本,返回其他语言原文;不生成翻译。',
|
|
28
|
+
'已有资料时优先使用完整 title 或 document_uid,可加 line;只有原句/名称时用 query 反查,source_language 默认 CN。',
|
|
29
|
+
'languages 指定目标语言(EN 英语、JP 日语、KR 韩语、TC 繁中等)。query 默认 exact,片段用 match_mode=literal。',
|
|
30
|
+
'相同原文可能对应多个不同译文,按来源消歧。角色档案按整条记录、档案库按整篇内容块对齐,中文行号不是目标语言行号。',
|
|
31
|
+
'默认不显示内部文本 ID;仅核验时 include_ids=true,后续可用 text_ids 精确查询。续页原样提交 page.continuation。',
|
|
32
|
+
].join(' ')
|
|
33
|
+
|
|
34
|
+
export const I18N_PARAMETERS = {
|
|
35
|
+
type: 'object', additionalProperties: false, required: ['languages'],
|
|
36
|
+
properties: {
|
|
37
|
+
game: { type: 'string', enum: ['endfield'], description: '当前支持终末地官方资料' },
|
|
38
|
+
query: { type: 'string', description: '名称或原句;仅有片段时使用 match_mode=literal' },
|
|
39
|
+
title: { type: 'string', description: '已有检索结果的完整篇章标题,与 query/document_uid/text_ids 四选一' },
|
|
40
|
+
document_uid: { type: 'string', description: '已有检索结果的文档定位,替代 title' },
|
|
41
|
+
line: { type: 'integer', description: '已有中文结果中的行号,只用于定位对应文本/记录' },
|
|
42
|
+
text_ids: { type: 'array', items: { type: 'string' }, description: '显式请求 ID 后可用于精确复查;普通查询不需要' },
|
|
43
|
+
source_language: { type: 'string', description: '原句的语言,默认 CN;也接受 en/ja/ko/zh-CN 等标准代码' },
|
|
44
|
+
languages: { type: 'array', items: { type: 'string' }, description: '1–4 种目标语言:CN/EN/JP/KR/TC/MX/BR/FR/DE/RU/IT/ID/TH/VN' },
|
|
45
|
+
match_mode: { type: 'string', enum: ['exact', 'literal'], description: '原句精确匹配或字面量片段搜索,默认 exact' },
|
|
46
|
+
include_ids: { type: 'boolean', description: '默认 false;仅调试、核验或精确复查时返回内部文本 ID 与来源字段' },
|
|
47
|
+
max_matches: { type: 'integer', description: '每页候选数,默认 5,最多 20' },
|
|
48
|
+
max_chars: { type: 'integer', description: '每页原文和译文的字符预算,默认 16000,范围 1000–48000' },
|
|
49
|
+
data_version: { type: 'string', description: '绑定已有结果的资料版本,避免混用版本' },
|
|
50
|
+
after: { type: 'object', additionalProperties: false,
|
|
51
|
+
required: ['data_version', 'request_hash', 'position', 'text_offset'],
|
|
52
|
+
properties: { data_version: { type: 'string' }, request_hash: { type: 'string' },
|
|
53
|
+
position: { type: 'integer' }, text_offset: { type: 'integer' } },
|
|
54
|
+
description: '仅原样提交工具返回的续页位置;长记录会明确分块返回' },
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const I18N_OUTPUT_SCHEMA = {
|
|
59
|
+
type: 'object', properties: {
|
|
60
|
+
status: { type: 'string', enum: ['ok', 'not_found', 'unmapped_source', 'unavailable'] },
|
|
61
|
+
game: { type: 'string' }, data_version: { type: 'string' }, game_version: { type: 'string' },
|
|
62
|
+
source_language: { type: 'string' }, languages: { type: 'array', items: { type: 'string' } },
|
|
63
|
+
matches: { type: 'array', items: { type: 'object', properties: {
|
|
64
|
+
source: { type: 'object' }, translations: { type: 'object' },
|
|
65
|
+
references: { type: 'array', items: { type: 'object' } }, reference_count: { type: 'integer' },
|
|
66
|
+
text_id: { type: 'string' }, source_fields: { type: 'array', items: { type: 'object' } },
|
|
67
|
+
} } },
|
|
68
|
+
page: { type: 'object' }, message: { type: 'string' }, note: { type: 'string' },
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function language(value) {
|
|
73
|
+
if (typeof value !== 'string') throw fault('INVALID_REQUEST', '语言代码必须是字符串')
|
|
74
|
+
const result = aliases[value.toLowerCase()] || value.toUpperCase()
|
|
75
|
+
if (!LOCALIZATION_LANGUAGES.includes(result)) throw fault('INVALID_REQUEST', `不支持的语言:${value}`)
|
|
76
|
+
return result
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function integer(value, minimum, maximum, name) {
|
|
80
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
81
|
+
throw fault('INVALID_REQUEST', `${name} 必须在 ${minimum}–${maximum} 之间`)
|
|
82
|
+
}
|
|
83
|
+
return value
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function normalizeI18nRequest(args) {
|
|
87
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)
|
|
88
|
+
|| Object.keys(args).some((key) => !Object.hasOwn(I18N_PARAMETERS.properties, key))) {
|
|
89
|
+
throw fault('INVALID_REQUEST', 'corpus_i18n 包含不支持的参数')
|
|
90
|
+
}
|
|
91
|
+
if (args.game !== undefined && args.game !== 'endfield') throw fault('INVALID_REQUEST', '当前仅支持终末地官方本地化')
|
|
92
|
+
const locators = ['query', 'title', 'document_uid', 'text_ids'].filter((key) => args[key] !== undefined)
|
|
93
|
+
if (locators.length !== 1) throw fault('INVALID_REQUEST', 'query/title/document_uid/text_ids 必须且只能提供一种')
|
|
94
|
+
const locator = locators[0]
|
|
95
|
+
const request = { game: 'endfield' }
|
|
96
|
+
if (locator === 'text_ids') {
|
|
97
|
+
if (!Array.isArray(args.text_ids) || !args.text_ids.length || args.text_ids.length > 20
|
|
98
|
+
|| args.text_ids.some((id) => typeof id !== 'string' || !/^-?[0-9]{1,20}$/u.test(id))) {
|
|
99
|
+
throw fault('INVALID_REQUEST', 'text_ids 必须是 1–20 个十进制字符串 ID,不能使用数字')
|
|
100
|
+
}
|
|
101
|
+
request.text_ids = [...new Set(args.text_ids)]
|
|
102
|
+
} else {
|
|
103
|
+
if (typeof args[locator] !== 'string' || !args[locator].trim() || args[locator].length > 12000) {
|
|
104
|
+
throw fault('INVALID_REQUEST', `${locator} 必须是非空字符串,最多 12000 字符`)
|
|
105
|
+
}
|
|
106
|
+
request[locator] = args[locator].trim()
|
|
107
|
+
if (locator === 'query' && !normalize(request.query)) throw fault('INVALID_REQUEST', 'query 必须包含可查询的文本')
|
|
108
|
+
if (locator === 'document_uid' && !/^doc_[A-Za-z0-9_-]{16}$/u.test(request.document_uid)) {
|
|
109
|
+
throw fault('INVALID_REQUEST', 'document_uid 必须来自检索结果')
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (args.line !== undefined) {
|
|
113
|
+
if (!['title', 'document_uid'].includes(locator)) throw fault('INVALID_REQUEST', 'line 只能与文档定位一起使用')
|
|
114
|
+
request.line = integer(args.line, 1, 100_000_000, 'line')
|
|
115
|
+
}
|
|
116
|
+
if (!Array.isArray(args.languages) || args.languages.length < 1 || args.languages.length > 4) {
|
|
117
|
+
throw fault('INVALID_REQUEST', 'languages 必须包含 1–4 种目标语言')
|
|
118
|
+
}
|
|
119
|
+
request.source_language = language(args.source_language ?? 'CN')
|
|
120
|
+
request.languages = [...new Set(args.languages.map(language))]
|
|
121
|
+
request.match_mode = args.match_mode ?? 'exact'
|
|
122
|
+
if (!['exact', 'literal'].includes(request.match_mode)) throw fault('INVALID_REQUEST', 'match_mode 只支持 exact/literal')
|
|
123
|
+
if (args.include_ids !== undefined && typeof args.include_ids !== 'boolean') throw fault('INVALID_REQUEST', 'include_ids 必须是布尔值')
|
|
124
|
+
request.include_ids = args.include_ids ?? false
|
|
125
|
+
request.max_matches = integer(args.max_matches ?? 5, 1, 20, 'max_matches')
|
|
126
|
+
request.max_chars = integer(args.max_chars ?? 16000, 1000, 48000, 'max_chars')
|
|
127
|
+
if (args.data_version !== undefined) {
|
|
128
|
+
if (typeof args.data_version !== 'string' || !/^[0-9a-f]{64}$/u.test(args.data_version)) throw fault('INVALID_REQUEST', 'data_version 必须是有效的 SHA-256')
|
|
129
|
+
request.data_version = args.data_version
|
|
130
|
+
}
|
|
131
|
+
if (args.after !== undefined) {
|
|
132
|
+
const after = args.after
|
|
133
|
+
if (!after || typeof after !== 'object' || Array.isArray(after)
|
|
134
|
+
|| Object.keys(after).sort().join() !== 'data_version,position,request_hash,text_offset'
|
|
135
|
+
|| !/^[0-9a-f]{64}$/u.test(after.data_version) || !/^[0-9a-f]{64}$/u.test(after.request_hash)) {
|
|
136
|
+
throw fault('INVALID_REQUEST', 'after 必须原样使用工具返回的续页位置')
|
|
137
|
+
}
|
|
138
|
+
request.after = { ...after,
|
|
139
|
+
position: integer(after.position, 0, 1_000_000, 'after.position'),
|
|
140
|
+
text_offset: integer(after.text_offset, 0, 10_000_000, 'after.text_offset') }
|
|
141
|
+
}
|
|
142
|
+
return request
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function checkpoint(store, snapshot, signal) {
|
|
146
|
+
assertCorpusVersion(store, snapshot)
|
|
147
|
+
if (signal?.aborted) throw fault('CANCELLED', '本地化查询已取消')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function stateFor(store) {
|
|
151
|
+
let state = states.get(store)
|
|
152
|
+
if (!state || state.generation !== store._generation || state.version !== store.dataVersion) {
|
|
153
|
+
state = { generation: store._generation, version: store.dataVersion,
|
|
154
|
+
catalog: null, languages: new Map(), pending: new Map() }
|
|
155
|
+
states.set(store, state)
|
|
156
|
+
}
|
|
157
|
+
return state
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function loadRows(store, snapshot, asset) {
|
|
161
|
+
try {
|
|
162
|
+
const bytes = await store._readPacked(PACK, asset.path, store.releaseId, asset)
|
|
163
|
+
assertCorpusVersion(store, snapshot)
|
|
164
|
+
return bytes.toString('utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line))
|
|
165
|
+
} catch (error) {
|
|
166
|
+
assertCorpusVersion(store, snapshot)
|
|
167
|
+
throw fault('INDEX_CORRUPT', `本地化附件读取失败:${error.message}`)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function catalogFor(store, state, meta, snapshot) {
|
|
172
|
+
if (!state.catalog) {
|
|
173
|
+
state.catalog = (async () => {
|
|
174
|
+
const rows = await loadRows(store, snapshot, meta.catalog)
|
|
175
|
+
const byId = new Map(), byDocument = new Map()
|
|
176
|
+
for (const entry of rows) {
|
|
177
|
+
if (typeof entry.text_id !== 'string' || !/^-?[0-9]{1,20}$/u.test(entry.text_id)
|
|
178
|
+
|| byId.has(entry.text_id) || !Array.isArray(entry.references) || !entry.references.length
|
|
179
|
+
|| !Array.isArray(entry.sources)) throw fault('INDEX_CORRUPT', '本地化目录记录无效')
|
|
180
|
+
byId.set(entry.text_id, entry)
|
|
181
|
+
for (const ref of entry.references) {
|
|
182
|
+
const doc = store.documents.get(ref.document_id)?.document
|
|
183
|
+
if (!doc || documentGame(doc) !== 'endfield'
|
|
184
|
+
|| store.documents.get(ref.document_id)?.packId !== PACK
|
|
185
|
+
|| !Number.isSafeInteger(ref.line_start) || !Number.isSafeInteger(ref.line_end)
|
|
186
|
+
|| ref.line_start < 1 || ref.line_end < ref.line_start || ref.line_end > doc.line_count
|
|
187
|
+
|| (ref.source_order !== undefined && (!Number.isSafeInteger(ref.source_order) || ref.source_order < 1))
|
|
188
|
+
|| !fields.includes(ref.field) || !['text', 'record', 'document'].includes(ref.alignment)) {
|
|
189
|
+
throw fault('INDEX_CORRUPT', '本地化目录指向无效的官方来源')
|
|
190
|
+
}
|
|
191
|
+
const ids = byDocument.get(ref.document_id) || new Set()
|
|
192
|
+
ids.add(entry.text_id)
|
|
193
|
+
byDocument.set(ref.document_id, ids)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (rows.length !== meta.text_count) throw fault('INDEX_CORRUPT', '本地化目录数量不一致')
|
|
197
|
+
return { rows, byId, byDocument }
|
|
198
|
+
})().catch((error) => { state.catalog = null; throw error })
|
|
199
|
+
}
|
|
200
|
+
return state.catalog
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function dictionaryFor(store, state, meta, catalog, lang, snapshot) {
|
|
204
|
+
if (!meta.languages[lang]) return null
|
|
205
|
+
if (state.languages.has(lang)) {
|
|
206
|
+
const cached = state.languages.get(lang)
|
|
207
|
+
state.languages.delete(lang); state.languages.set(lang, cached)
|
|
208
|
+
return cached
|
|
209
|
+
}
|
|
210
|
+
if (!state.pending.has(lang)) {
|
|
211
|
+
const promise = (async () => {
|
|
212
|
+
const rows = await loadRows(store, snapshot, meta.languages[lang])
|
|
213
|
+
const byId = new Map(), exact = new Map(), search = []
|
|
214
|
+
for (const row of rows) {
|
|
215
|
+
if (!Array.isArray(row) || row.length !== 2 || typeof row[0] !== 'string'
|
|
216
|
+
|| typeof row[1] !== 'string' || !catalog.byId.has(row[0]) || byId.has(row[0])) {
|
|
217
|
+
throw fault('INDEX_CORRUPT', '本地化字典记录无效')
|
|
218
|
+
}
|
|
219
|
+
const [id, text] = row
|
|
220
|
+
byId.set(id, text)
|
|
221
|
+
const key = normalize(text)
|
|
222
|
+
if (key) {
|
|
223
|
+
const ids = exact.get(key) || []; ids.push(id); exact.set(key, ids)
|
|
224
|
+
search.push([id, key])
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (byId.size !== meta.text_count) throw fault('INDEX_CORRUPT', '本地化字典数量不一致')
|
|
228
|
+
assertCorpusVersion(store, snapshot)
|
|
229
|
+
const value = { byId, exact, search }
|
|
230
|
+
state.languages.set(lang, value)
|
|
231
|
+
while (state.languages.size > 4) state.languages.delete(state.languages.keys().next().value)
|
|
232
|
+
return value
|
|
233
|
+
})().finally(() => state.pending.delete(lang))
|
|
234
|
+
state.pending.set(lang, promise)
|
|
235
|
+
}
|
|
236
|
+
return state.pending.get(lang)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function publicReference(store, ref) {
|
|
240
|
+
const doc = store.documents.get(ref.document_id).document
|
|
241
|
+
const title = naturalDocumentTitle(doc)
|
|
242
|
+
return { title, document_uid: documentUid(doc.document_id), line_start: ref.line_start,
|
|
243
|
+
line_end: ref.line_end, field: ref.field, alignment: ref.alignment,
|
|
244
|
+
citation: `《${title}》中文定位第 ${ref.line_start}${ref.line_end === ref.line_start ? '' : `–${ref.line_end}`} 行` }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function chunk(text, offset, width, available) {
|
|
248
|
+
if (!available) return { status: 'unavailable' }
|
|
249
|
+
if (!text?.trim()) return { status: 'missing_localization' }
|
|
250
|
+
const chars = [...text]
|
|
251
|
+
const start = Math.min(offset, chars.length), end = Math.min(offset + width, chars.length)
|
|
252
|
+
return { status: 'available', text: chars.slice(start, end).join(''),
|
|
253
|
+
...(offset || end < chars.length ? { character_start: start, character_end: end,
|
|
254
|
+
total_characters: chars.length, truncated: end < chars.length } : {}) }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function executeI18n(store, raw, { signal, enabledGames = ['endfield'] } = {}) {
|
|
258
|
+
const request = normalizeI18nRequest(raw)
|
|
259
|
+
if (!enabledGames.includes('endfield')) throw fault('INVALID_REQUEST', '当前未启用终末地资料')
|
|
260
|
+
await store.ready()
|
|
261
|
+
const snapshot = corpusVersionSnapshot(store)
|
|
262
|
+
checkpoint(store, snapshot, signal)
|
|
263
|
+
if ((request.data_version && request.data_version !== snapshot.dataVersion)
|
|
264
|
+
|| (request.after && request.after.data_version !== snapshot.dataVersion)) {
|
|
265
|
+
throw fault('PACKAGE_VERSION_MISMATCH', '本地化查询所属资料版本已变化,请重新查询')
|
|
266
|
+
}
|
|
267
|
+
const { after, data_version: _version, ...identity } = request
|
|
268
|
+
const requestHash = sha256(JSON.stringify(identity))
|
|
269
|
+
if (after && after.request_hash !== requestHash) throw fault('INVALID_REQUEST', '续页参数与原查询不一致')
|
|
270
|
+
const base = { game: 'endfield', data_version: snapshot.dataVersion,
|
|
271
|
+
source_language: request.source_language, languages: request.languages }
|
|
272
|
+
const pack = store.packs.get(PACK), meta = pack?.localization
|
|
273
|
+
if (!meta) return { ...base, status: 'unavailable', matches: [],
|
|
274
|
+
message: '当前终末地官方资料包未包含多语言附件,请安装带本地化数据的版本。',
|
|
275
|
+
page: { has_more: false, continuation: null } }
|
|
276
|
+
base.game_version = meta.game_version
|
|
277
|
+
const state = stateFor(store)
|
|
278
|
+
const catalog = await catalogFor(store, state, meta, snapshot)
|
|
279
|
+
checkpoint(store, snapshot, signal)
|
|
280
|
+
const requestedLanguages = [...new Set([request.source_language, ...request.languages])]
|
|
281
|
+
const dictionaries = new Map(await Promise.all(requestedLanguages.map(async (lang) =>
|
|
282
|
+
[lang, await dictionaryFor(store, state, meta, catalog, lang, snapshot)])))
|
|
283
|
+
checkpoint(store, snapshot, signal)
|
|
284
|
+
const source = dictionaries.get(request.source_language)
|
|
285
|
+
if (!source) return { ...base, status: 'unavailable', matches: [],
|
|
286
|
+
message: '当前附件没有查询所用的来源语言。', page: { has_more: false, continuation: null } }
|
|
287
|
+
let ids = [], documentId = null
|
|
288
|
+
if (request.query) {
|
|
289
|
+
const query = normalize(request.query)
|
|
290
|
+
if (request.match_mode === 'exact') ids = source.exact.get(query) || []
|
|
291
|
+
else {
|
|
292
|
+
for (let i = 0; i < source.search.length; i += 1) {
|
|
293
|
+
if ((i & 2047) === 0) { await new Promise((resolve) => setImmediate(resolve)); checkpoint(store, snapshot, signal) }
|
|
294
|
+
const [id, text] = source.search[i]
|
|
295
|
+
if (text.includes(query)) ids.push(id)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} else if (request.text_ids) ids = request.text_ids.filter((id) => catalog.byId.has(id))
|
|
299
|
+
else {
|
|
300
|
+
const found = request.document_uid ? await store.getDocumentByUid(request.document_uid)
|
|
301
|
+
: await store.getDocumentByTitle(request.title)
|
|
302
|
+
checkpoint(store, snapshot, signal)
|
|
303
|
+
if (!found) throw fault('DOCUMENT_NOT_FOUND', '找不到指定资料,请使用检索结果中的完整定位')
|
|
304
|
+
if (found.packId !== PACK) throw fault('INVALID_REQUEST', '该资料不是终末地官方原文')
|
|
305
|
+
documentId = found.record.document.document_id
|
|
306
|
+
if (request.line && request.line > found.record.lines.length) throw fault('LINE_RANGE_INVALID', '中文定位行号超出文档范围')
|
|
307
|
+
ids = [...(catalog.byDocument.get(documentId) || [])].filter((id) => {
|
|
308
|
+
const text = source.byId.get(id) || ''
|
|
309
|
+
// An absent source translation still has a valid identity. Omit only
|
|
310
|
+
// image/markup-only blocks, retaining explicit missing_localization.
|
|
311
|
+
return (!text.trim() || normalize(text)) && catalog.byId.get(id).references.some((ref) => ref.document_id === documentId
|
|
312
|
+
&& (!request.line || (ref.line_start <= request.line && ref.line_end >= request.line)))
|
|
313
|
+
})
|
|
314
|
+
const sourceOrder = (entry) => {
|
|
315
|
+
const ref = entry.references.find((item) => item.document_id === documentId
|
|
316
|
+
&& (!request.line || (item.line_start <= request.line && item.line_end >= request.line)))
|
|
317
|
+
const fieldOrder = ['text', 'title', 'name', 'actor', 'hint'].indexOf(ref.field)
|
|
318
|
+
if (ref.alignment !== 'document') return [ref.line_start, fieldOrder, '']
|
|
319
|
+
// Archive Markdown does not have exact line alignment. Preserve the order
|
|
320
|
+
// of the original rich-content/radio blocks rather than sorting int64 IDs.
|
|
321
|
+
return [ref.source_order ?? 0, 0, '']
|
|
322
|
+
}
|
|
323
|
+
ids.sort((left, right) => {
|
|
324
|
+
const a = sourceOrder(catalog.byId.get(left)), b = sourceOrder(catalog.byId.get(right))
|
|
325
|
+
return a[0] - b[0] || a[1] - b[1] || a[2].localeCompare(b[2], 'en', { numeric: true })
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
// Preserve source/catalog order for document reads and deterministic query pages.
|
|
329
|
+
let position = after?.position ?? 0, offset = after?.text_offset ?? 0
|
|
330
|
+
if (position > ids.length || (position === ids.length && offset)) throw fault('INVALID_REQUEST', '续页位置超出查询结果')
|
|
331
|
+
const matches = []
|
|
332
|
+
let budget = request.max_chars
|
|
333
|
+
while (position < ids.length && matches.length < request.max_matches && budget >= 100) {
|
|
334
|
+
checkpoint(store, snapshot, signal)
|
|
335
|
+
const id = ids[position], entry = catalog.byId.get(id)
|
|
336
|
+
const width = Math.floor(budget / (request.languages.length + 1))
|
|
337
|
+
const sourceValue = chunk(source.byId.get(id), offset, width, true)
|
|
338
|
+
const translations = Object.fromEntries(request.languages.map((lang) => [lang,
|
|
339
|
+
chunk(dictionaries.get(lang)?.byId.get(id), offset, width, Boolean(dictionaries.get(lang)))]))
|
|
340
|
+
const values = [sourceValue, ...Object.values(translations)]
|
|
341
|
+
const maximumLength = Math.max(...requestedLanguages.map((lang) => [...(dictionaries.get(lang)?.byId.get(id) || '')].length))
|
|
342
|
+
if (offset && offset >= maximumLength) throw fault('INVALID_REQUEST', '记录续页偏移超出正文')
|
|
343
|
+
const refs = entry.references.filter((ref) => !documentId || ref.document_id === documentId)
|
|
344
|
+
matches.push({ source: { language: request.source_language, ...sourceValue }, translations,
|
|
345
|
+
references: refs.slice(0, 8).map((ref) => publicReference(store, ref)), reference_count: refs.length,
|
|
346
|
+
...(request.include_ids ? { text_id: id, source_fields: entry.sources.slice(0, 8),
|
|
347
|
+
source_field_count: entry.sources.length } : {}) })
|
|
348
|
+
budget -= values.reduce((sum, value) => sum + [...(value.text || '')].length, 0)
|
|
349
|
+
if (values.some((value) => value.truncated)) { offset += width; break }
|
|
350
|
+
position += 1; offset = 0
|
|
351
|
+
}
|
|
352
|
+
checkpoint(store, snapshot, signal)
|
|
353
|
+
const hasMore = position < ids.length
|
|
354
|
+
return { ...base, status: ids.length ? 'ok' : documentId ? 'unmapped_source' : 'not_found',
|
|
355
|
+
matches, total_matches: ids.length,
|
|
356
|
+
...(request.text_ids ? { missing_text_ids: request.text_ids.filter((id) => !catalog.byId.has(id)) } : {}),
|
|
357
|
+
note: '返回官方本地化原文;引用请注明语言。中文行号仅用于定位,record/document 对齐不表示跨语言逐行对应。',
|
|
358
|
+
page: { has_more: hasMore, continuation: hasMore ? { ...identity,
|
|
359
|
+
data_version: snapshot.dataVersion, after: { data_version: snapshot.dataVersion,
|
|
360
|
+
request_hash: requestHash, position, text_offset: offset } } : null } }
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function renderI18n(_args, value) {
|
|
364
|
+
return [{ type: 'text', text: JSON.stringify(value) }]
|
|
365
|
+
}
|