Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2299199ba | |||
| ec062817dc | |||
| f56e10748e | |||
| ac1bd03426 | |||
| 07f557ca30 | |||
| 7f189aa741 | |||
| d048a33c55 | |||
| 4e99dfdd92 | |||
| 5d54da9048 | |||
| d96db4e1f0 | |||
| c40712701c | |||
| c9595312ab | |||
| 91e12e4ba1 | |||
| 921a5a3156 | |||
| 2ab32b258c | |||
| 175c1dbaed | |||
| e9bfbfe74f | |||
| 6782cfcd87 | |||
| eaf84ad605 | |||
| 65e3f5b48e | |||
| 9519b92771 | |||
| 6257027a13 | |||
| cde9194ed5 | |||
| 1c8cf13f67 | |||
| 74a8942999 | |||
| 7f8f09a3ac | |||
| 70f93c3d29 | |||
| 4aa2e136b2 | |||
| 3ae30a5263 | |||
| 4da26911fe | |||
| df1daee6ab | |||
| 9d1a26c8e0 | |||
| 8a4970742c | |||
| b50a6a2f95 | |||
| 4646b51459 | |||
| a72b7bc1e1 | |||
| 7aca648396 | |||
| 36129b15d1 | |||
| 97ca95ba16 | |||
| ad129e15d5 | |||
| c8371a3fec | |||
| 321fef2f63 | |||
| eb9ba5eefa | |||
| c0524832d0 | |||
| e78610c702 | |||
| 5c0a44a71e | |||
| c859ddc50c | |||
| 5f3ca9ae0d | |||
| 782879d73e | |||
| d5cd0300da | |||
| 85a7ec546c | |||
| 37d34f8406 | |||
| fba22ac1aa | |||
| 0d42f9ca34 | |||
| 7a410d4124 | |||
| 945ede2583 | |||
| 469cd8bc37 | |||
| c1ca11ab25 | |||
| 042b1fb43b | |||
| c49ff67d51 | |||
| 2324bf0c26 | |||
| 4148e8b861 | |||
| 3c893cd0d9 | |||
| 2a7b6d9395 | |||
| f04d14dcf1 | |||
| 4bb64d35da | |||
| cb92587449 |
@@ -59,7 +59,7 @@ jobs:
|
|||||||
throw "Could not find a downloadable LSLib zip asset in the latest release."
|
throw "Could not find a downloadable LSLib zip asset in the latest release."
|
||||||
}
|
}
|
||||||
|
|
||||||
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile ".tools/lslib/lslib.zip"
|
Invoke-WebRequest -UseBasicParsing -Uri $asset.browser_download_url -OutFile ".tools/lslib/lslib.zip"
|
||||||
Expand-Archive -LiteralPath ".tools/lslib/lslib.zip" -DestinationPath ".tools/lslib" -Force
|
Expand-Archive -LiteralPath ".tools/lslib/lslib.zip" -DestinationPath ".tools/lslib" -Force
|
||||||
|
|
||||||
- name: Build .pak
|
- name: Build .pak
|
||||||
@@ -157,4 +157,4 @@ jobs:
|
|||||||
Accept = "application/json"
|
Accept = "application/json"
|
||||||
}
|
}
|
||||||
|
|
||||||
Invoke-WebRequest -Method Post -Uri "$apiBase/releases/$($release.id)/assets?name=$([uri]::EscapeDataString($assetName))" -Headers $uploadHeaders -ContentType "application/octet-stream" -InFile $zipPath
|
Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$apiBase/releases/$($release.id)/assets?name=$([uri]::EscapeDataString($assetName))" -Headers $uploadHeaders -ContentType "application/octet-stream" -InFile $zipPath
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
build/
|
build/
|
||||||
build-stage*
|
build-stage*
|
||||||
.tools/
|
.tools/
|
||||||
|
.cache/
|
||||||
*.pak
|
*.pak
|
||||||
*.tmp
|
*.tmp
|
||||||
*.temp
|
*.temp
|
||||||
|
|||||||
137
ACTIONS.md
Normal file
137
ACTIONS.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# ACTIONS.md
|
||||||
|
|
||||||
|
VERSION: 3
|
||||||
|
MODE: machine-first
|
||||||
|
LANG: ru
|
||||||
|
|
||||||
|
ROUTING:
|
||||||
|
- match_user_request_to_action_id: true
|
||||||
|
- if_match: propose_action
|
||||||
|
- if_no_match: ignore_actions_md
|
||||||
|
- if_no_match_user_message: none
|
||||||
|
|
||||||
|
PROPOSE_RULE:
|
||||||
|
- prompt_template: "Приступить к выполнению '{action_id}'?"
|
||||||
|
- require_user_confirmation: true
|
||||||
|
- execute_without_confirmation: false
|
||||||
|
|
||||||
|
EXECUTION_BASELINE:
|
||||||
|
- enforce_agents_md: true
|
||||||
|
- minimal_non_breaking_changes: true
|
||||||
|
- steps_count_range: [3, 7]
|
||||||
|
- before_commit_push: request_user_approval
|
||||||
|
- prefer_existing_repo_scripts_over_manual_work: true
|
||||||
|
|
||||||
|
REPORT_FORMAT:
|
||||||
|
- done
|
||||||
|
- changed_files
|
||||||
|
- checks
|
||||||
|
- remaining
|
||||||
|
|
||||||
|
ACTIONS:
|
||||||
|
translation:diff:
|
||||||
|
intent: fetch_upstream_english_and_compare_with_ru
|
||||||
|
inputs:
|
||||||
|
- AGENTS.md::Canonical Paths::Upstream English reference
|
||||||
|
- Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml
|
||||||
|
plan:
|
||||||
|
- run_scripts/get-upstream-english.ps1_and_wait_until_output_exists
|
||||||
|
- run_scripts/compare-translation.ps1_after_upstream_download_only
|
||||||
|
- classify_diff_into_missing_changed_stale
|
||||||
|
- write_machine_readable_and_markdown_reports_for_local_review
|
||||||
|
checks:
|
||||||
|
- xml_valid
|
||||||
|
- cache_path_gitignored
|
||||||
|
- local_only_no_ci_workflow_required
|
||||||
|
- translation_steps_not_parallelized_when_file_dependency_exists
|
||||||
|
outputs:
|
||||||
|
- .cache/upstream/english.xml
|
||||||
|
- build/translation-diff/summary.json
|
||||||
|
- build/translation-diff/summary.md
|
||||||
|
- build/translation-diff/candidates.json
|
||||||
|
translation:apply:
|
||||||
|
intent: apply_translation_edits_to_russian_xml
|
||||||
|
inputs:
|
||||||
|
- Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml
|
||||||
|
- build/translation-diff/candidates.json
|
||||||
|
- prepared_update_texts_for_updates_and_optional_adds
|
||||||
|
plan:
|
||||||
|
- create_temporary_copy_of_russian_xml
|
||||||
|
- load_candidate_edit_file_and_temporary_xml
|
||||||
|
- fail_if_add_entry_has_empty_text
|
||||||
|
- apply_updates_and_optional_new_entries_by_contentuid
|
||||||
|
- write_utf8_bom_xml_to_temporary_copy
|
||||||
|
- validate_temporary_xml_via_separate_script
|
||||||
|
- replace_original_russian_xml_after_successful_validation
|
||||||
|
- report_changed_entries
|
||||||
|
checks:
|
||||||
|
- xml_valid
|
||||||
|
- contentuid_uniqueness_preserved
|
||||||
|
- only_requested_entries_changed
|
||||||
|
- no_partial_replace_on_validation_failure
|
||||||
|
outputs:
|
||||||
|
- Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml
|
||||||
|
translation:update:
|
||||||
|
intent: sync_ru_translation_with_upstream
|
||||||
|
inputs:
|
||||||
|
- Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml
|
||||||
|
- glossary/glossary.normalized.json
|
||||||
|
- AGENTS.md::Canonical Paths::Upstream English reference
|
||||||
|
plan:
|
||||||
|
- run_translation:diff_sequentially
|
||||||
|
- if_summary_has_no_missing_no_version_mismatch_no_stale_report_translation_up_to_date_and_stop
|
||||||
|
- if_diff_exists_stop_after_generating_build/translation-diff/candidates.json_until_prepared_edits_are_provided_explicitly
|
||||||
|
- review_build/translation-diff/candidates.json_before_apply
|
||||||
|
- reuse_glossary_for_term_consistency_when_preparing_texts
|
||||||
|
- run_translation:apply_only_after_candidate_texts_are_filled_and_explicit_edits_path_is_passed
|
||||||
|
checks:
|
||||||
|
- xml_valid
|
||||||
|
- glossary_consistency
|
||||||
|
- scope_limited_to_localization_and_allowed_metadata
|
||||||
|
- no_upstream_download_compare_race_condition
|
||||||
|
outputs:
|
||||||
|
- message: translation_up_to_date
|
||||||
|
- build/translation-diff/summary.json
|
||||||
|
- build/translation-diff/summary.md
|
||||||
|
- build/translation-diff/candidates.json
|
||||||
|
- Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml
|
||||||
|
- optional: Mods/DnD 5.5e AIO Russian/meta.lsx (release-only)
|
||||||
|
after_success:
|
||||||
|
- suggest_action: meta:sync-parent
|
||||||
|
reason: "Обновить версию зависимости из родительского мода (актуальный Version64 и связанные поля зависимости)."
|
||||||
|
|
||||||
|
action:report:
|
||||||
|
intent: unified_task_report
|
||||||
|
inputs:
|
||||||
|
- task_context
|
||||||
|
- modified_files
|
||||||
|
- verification_results
|
||||||
|
plan:
|
||||||
|
- summarize_done
|
||||||
|
- list_changed_files
|
||||||
|
- list_checks
|
||||||
|
- list_remaining
|
||||||
|
checks:
|
||||||
|
- concise
|
||||||
|
- factual
|
||||||
|
- no_unverified_claims
|
||||||
|
outputs:
|
||||||
|
- final_user_report
|
||||||
|
meta:sync-parent:
|
||||||
|
intent: sync_dependency_moduleshortdesc_from_parent_meta
|
||||||
|
inputs:
|
||||||
|
- parent_meta_git_url (optional; defaults to upstream)
|
||||||
|
- Mods/DnD 5.5e AIO Russian/meta.lsx
|
||||||
|
plan:
|
||||||
|
- read_parent_moduleinfo_fields
|
||||||
|
- validate_required_fields_folder_md5_name_publishhandle_uuid_version64
|
||||||
|
- update_target_dependencies_moduleshortdesc_fields
|
||||||
|
- validate_xml_structure
|
||||||
|
- report_changed_fields
|
||||||
|
checks:
|
||||||
|
- xml_valid
|
||||||
|
- required_parent_fields_present
|
||||||
|
- only_dependencies_moduleshortdesc_changed
|
||||||
|
outputs:
|
||||||
|
- Mods/DnD 5.5e AIO Russian/meta.lsx
|
||||||
|
|
||||||
91
AGENTS.md
Normal file
91
AGENTS.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## General Rules (MUST)
|
||||||
|
|
||||||
|
### Git Collaboration Policy (General)
|
||||||
|
- Commit/push only after explicit user approval.
|
||||||
|
- After approval: commit and push immediately.
|
||||||
|
- Branch switch prompt (`fix/*` or `feat/*`): ask at dialogue start; reuse the explicit user decision for all subsequent fix/feature tasks in the same dialogue.
|
||||||
|
- Pending clarification/approval question: ask once, in a single short message; do not repeat the same pending question in a separate final message.
|
||||||
|
- After finishing work in `fix/*` or `feat/*`: propose either
|
||||||
|
1. creating an MR into `main`, or
|
||||||
|
2. merging to `main` immediately and deleting the `fix/*`/`feat/*` branch.
|
||||||
|
- If push fails: retry up to 2 more times with 3s pause.
|
||||||
|
- Approval prompts for pending actions: short direct phrasing, no soft/opening phrases; response format is mandatory:
|
||||||
|
- binary action: yes/no question.
|
||||||
|
- multiple actions/combinations: numbered options only.
|
||||||
|
|
||||||
|
### Cleanup (General)
|
||||||
|
- Do not leave temporary/debug artifacts in repo.
|
||||||
|
- Remove additional debug/temp dirs unless user asked to keep them.
|
||||||
|
|
||||||
|
### Rules Maintenance (General)
|
||||||
|
- For changes to rules files (`AGENTS.md`, `ACTIONS.md`): prefer optimized, compressed edits for AI-agent execution (machine-readable, unambiguous).
|
||||||
|
- Keep rule updates minimal and non-duplicative: merge overlapping points, remove redundancy, preserve intent.
|
||||||
|
|
||||||
|
### Communication (General)
|
||||||
|
- Project file links in user-facing Markdown: relative paths, `/` separators, spaces encoded as `%20`.
|
||||||
|
|
||||||
|
## Project-Specific Rules (MUST)
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
- Repository purpose: standalone Russian localization mod only.
|
||||||
|
- Allowed domain: localization content + packaging/release metadata.
|
||||||
|
- Forbidden: gameplay logic, Script Extender content, unrelated assets.
|
||||||
|
- Keep repository source-only.
|
||||||
|
- Never commit `.pak` or temporary build artifacts.
|
||||||
|
|
||||||
|
### Canonical Paths
|
||||||
|
- Mod sources: `Mods/DnD 5.5e AIO Russian`
|
||||||
|
- Russian localization: `Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml`
|
||||||
|
- Mod metadata: `Mods/DnD 5.5e AIO Russian/meta.lsx`
|
||||||
|
- Build script (single source of build truth): `scripts/build.ps1`
|
||||||
|
- CI workflow: `.gitea/workflows/build.yml`
|
||||||
|
- Glossary (primary terminology reference): `glossary/glossary.normalized.json`
|
||||||
|
- Action catalog and command playbooks: `ACTIONS.md`
|
||||||
|
- Upstream English reference: `https://github.com/Yoonmoonsik/dnd55e/blob/main/Mods/DnD2024_897914ef-5c96-053c-44af-0be823f895fe/Localization/English/english.xml`
|
||||||
|
|
||||||
|
### Packaging Invariants
|
||||||
|
- `.pak` must contain only BG3 mod structure under `Mods/...`.
|
||||||
|
- Required content in `.pak`:
|
||||||
|
- `Mods/DnD 5.5e AIO Russian/meta.lsx`
|
||||||
|
- `Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml`
|
||||||
|
- Must not leak into `.pak`: `.git`, `.gitea`, `scripts`, `tools`, `.tools`, `build`, staging dirs.
|
||||||
|
- Staging for packaging must be in `%TEMP%`, not in dot-prefixed repo dirs.
|
||||||
|
|
||||||
|
### Build/CI Contract
|
||||||
|
- CI workflow stays thin:
|
||||||
|
1. prepare workspace
|
||||||
|
2. download Divine
|
||||||
|
3. call `scripts/build.ps1`
|
||||||
|
4. publish tag archive
|
||||||
|
- Expected build outputs:
|
||||||
|
- `build/DnD 5.5e AIO Russian.pak`
|
||||||
|
- `build/info.json`
|
||||||
|
- `build/DnD 5.5e AIO Russian <tag>.zip` (for tag builds)
|
||||||
|
- Release ZIP must include only `.pak` + `info.json`.
|
||||||
|
- CI triggers: tag `v*` and manual dispatch; not every push to `main`.
|
||||||
|
|
||||||
|
### Version/Release Rules
|
||||||
|
- Read release version only from `save/region[@id="Config"]/node[@id="root"]/children/node[@id="ModuleInfo"]/attribute[@id="Version64"]` via explicit XML parsing.
|
||||||
|
- `PublishVersion` must not be changed during release preparation.
|
||||||
|
- Release tag must match the source-of-truth version.
|
||||||
|
- Decision logic before tagging:
|
||||||
|
1. If `ModuleInfo/Version64` was manually changed (e.g. BG3 Toolkit), use matching tag and release.
|
||||||
|
2. If `ModuleInfo/Version64` equals latest released version, bump version first (e.g. `scripts/set-version.ps1 -VersionTag <tag>`), commit, then create/push tag.
|
||||||
|
- `scripts/build.ps1` derives release `Version64` from tag and writes it to generated `info.json` and staged `meta.lsx`.
|
||||||
|
|
||||||
|
### info.json Contract
|
||||||
|
- Top-level keys: `Mods`, `MD5`.
|
||||||
|
- Per-mod keys: `Author`, `Name`, `Folder`, `Version`, `Description`, `UUID`, `Created`, `Dependencies`, `Group`.
|
||||||
|
- `Dependencies` is an array of UUIDs.
|
||||||
|
- Current dependency UUID: `897914ef-5c96-053c-44af-0be823f895fe`.
|
||||||
|
|
||||||
|
### Git Collaboration Policy (Project-Specific)
|
||||||
|
- Commit messages and comments: Russian.
|
||||||
|
- Commit message content: what was done (not what should be done).
|
||||||
|
- If changes affect `.pak` contents or build/release flow: propose releasing next version.
|
||||||
|
- For released versions in user-facing messages: provide direct archive link in Markdown format `[version](url)` when derivable (acceptable immediately after tag push, even before CI finishes).
|
||||||
|
|
||||||
|
### Cleanup (Project-Specific)
|
||||||
|
- Ignored/temp patterns include: `build/`, `build-stage*`, `.tools/`, `*.pak`.
|
||||||
BIN
Mods/DnD 5.5e AIO Russian/GUI/metadata.lsf
Normal file
BIN
Mods/DnD 5.5e AIO Russian/GUI/metadata.lsf
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,44 +1,77 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<save>
|
<save>
|
||||||
<version major="4" minor="8" revision="0" build="500"/>
|
<version major="4" minor="8" revision="0" build="500"/>
|
||||||
<region id="Config">
|
<region id="Config">
|
||||||
<node id="root">
|
<node id="root">
|
||||||
<children>
|
<children>
|
||||||
<node id="Dependencies">
|
<node id="Conflicts"/>
|
||||||
<children>
|
<node id="Dependencies">
|
||||||
<node id="ModuleShortDesc">
|
<children>
|
||||||
<attribute id="Folder" type="LSString" value=""/>
|
<node id="ModuleShortDesc">
|
||||||
<attribute id="MD5" type="LSString" value=""/>
|
<attribute id="Folder" type="LSString" value="DnD2024_897914ef-5c96-053c-44af-0be823f895fe"/>
|
||||||
<attribute id="Name" type="LSString" value="DnD 5.5e All-in-One BEYOND"/>
|
<attribute id="MD5" type="LSString" value="4bd42ca93f895d1ec521a286bea09ef2"/>
|
||||||
<attribute id="PublishHandle" type="uint64" value="0"/>
|
<attribute id="Name" type="LSString" value="DnD 5.5e All-in-One BEYOND"/>
|
||||||
<attribute id="UUID" type="guid" value="897914ef-5c96-053c-44af-0be823f895fe"/>
|
<attribute id="PublishHandle" type="uint64" value="4419649"/>
|
||||||
<attribute id="Version64" type="int64" value="36028797018963968"/>
|
<attribute id="UUID" type="guid" value="897914ef-5c96-053c-44af-0be823f895fe"/>
|
||||||
</node>
|
<attribute id="Version64" type="int64" value="144396675937468416"/>
|
||||||
</children>
|
</node>
|
||||||
|
</children>
|
||||||
|
</node>
|
||||||
|
<node id="ModuleInfo">
|
||||||
|
<attribute id="Author" type="LSString" value="Underslumber Team"/>
|
||||||
|
<attribute id="CharacterCreationLevelName" type="FixedString" value=""/>
|
||||||
|
<attribute id="Description" type="LSString" value="Русский перевод мода DnD 5.5e All-in-One BEYOND. Перевод ещё в разработке: AI помогает быстро обновлять тексты, а финальные правки и качество мы проверяем вручную."/>
|
||||||
|
<attribute id="FileSize" type="uint64" value="2488095"/>
|
||||||
|
<attribute id="Folder" type="LSString" value="DnD 5.5e AIO Russian"/>
|
||||||
|
<attribute id="LobbyLevelName" type="FixedString" value=""/>
|
||||||
|
<attribute id="MD5" type="LSString" value="c0a8f3412870277331306e0719fc6f77"/>
|
||||||
|
<attribute id="MenuLevelName" type="FixedString" value=""/>
|
||||||
|
<attribute id="Name" type="LSString" value="DnD 5.5e All-in-One BEYOND Russian Localization"/>
|
||||||
|
<attribute id="NumPlayers" type="uint8" value="4"/>
|
||||||
|
<attribute id="PhotoBooth" type="FixedString" value=""/>
|
||||||
|
<attribute id="PublishHandle" type="uint64" value="5965149"/>
|
||||||
|
<attribute id="StartupLevelName" type="FixedString" value=""/>
|
||||||
|
<attribute id="UUID" type="FixedString" value="6401e84d-daf2-416d-adeb-99c03a2487a6"/>
|
||||||
|
<attribute id="Version64" type="int64" value="281485714128896"/>
|
||||||
|
<children>
|
||||||
|
<node id="PublishVersion">
|
||||||
|
<attribute id="Version64" type="int64" value="281477124194304"/>
|
||||||
|
</node>
|
||||||
|
<node id="Scripts">
|
||||||
|
<children>
|
||||||
|
<node id="Script">
|
||||||
|
<attribute id="UUID" type="FixedString" value="1953f77d-a201-45d7-a194-9b84c34b8461"/>
|
||||||
|
<children>
|
||||||
|
<node id="Parameters">
|
||||||
|
<children>
|
||||||
|
<node id="Parameter">
|
||||||
|
<attribute id="MapKey" type="FixedString" value="HardcoreOnly"/>
|
||||||
|
<attribute id="Type" type="int32" value="1"/>
|
||||||
|
<attribute id="Value" type="LSString" value="0"/>
|
||||||
|
</node>
|
||||||
|
</children>
|
||||||
|
</node>
|
||||||
|
</children>
|
||||||
|
</node>
|
||||||
|
<node id="Script">
|
||||||
|
<attribute id="UUID" type="FixedString" value="0d6510f5-50a3-4ecd-83d8-134c9a640324"/>
|
||||||
|
<children>
|
||||||
|
<node id="Parameters">
|
||||||
|
<children>
|
||||||
|
<node id="Parameter">
|
||||||
|
<attribute id="MapKey" type="FixedString" value="HardcoreOnly"/>
|
||||||
|
<attribute id="Type" type="int32" value="1"/>
|
||||||
|
<attribute id="Value" type="LSString" value="0"/>
|
||||||
|
</node>
|
||||||
|
</children>
|
||||||
|
</node>
|
||||||
|
</children>
|
||||||
|
</node>
|
||||||
|
</children>
|
||||||
|
</node>
|
||||||
|
</children>
|
||||||
|
</node>
|
||||||
|
</children>
|
||||||
</node>
|
</node>
|
||||||
<node id="ModuleInfo">
|
</region>
|
||||||
<attribute id="Author" type="LSString" value="MikhailRaw"/>
|
|
||||||
<attribute id="CharacterCreationLevelName" type="FixedString" value=""/>
|
|
||||||
<attribute id="Description" type="LSString" value="Русская локализация мода, который добавляет и обновляет контент в соответствии с правилами DnD 5.5e и другими источниками, включая предыстории, классы, таланты, расы, заклинания и многое другое. Это отдельный мод локализации и он требует установленный оригинальный мод."/>
|
|
||||||
<attribute id="FileSize" type="uint64" value="0"/>
|
|
||||||
<attribute id="Folder" type="LSString" value="DnD 5.5e AIO Russian"/>
|
|
||||||
<attribute id="LobbyLevelName" type="FixedString" value=""/>
|
|
||||||
<attribute id="MD5" type="LSString" value=""/>
|
|
||||||
<attribute id="MenuLevelName" type="FixedString" value=""/>
|
|
||||||
<attribute id="Name" type="LSString" value="DnD 5.5e All-in-One BEYOND Russian Localization"/>
|
|
||||||
<attribute id="NumPlayers" type="uint8" value="4"/>
|
|
||||||
<attribute id="PhotoBooth" type="FixedString" value=""/>
|
|
||||||
<attribute id="PublishHandle" type="uint64" value="0"/>
|
|
||||||
<attribute id="StartupLevelName" type="FixedString" value=""/>
|
|
||||||
<attribute id="UUID" type="FixedString" value="6401e84d-daf2-416d-adeb-99c03a2487a6"/>
|
|
||||||
<attribute id="Version64" type="int64" value="36028797018963968"/>
|
|
||||||
<children>
|
|
||||||
<node id="PublishVersion">
|
|
||||||
<attribute id="Version64" type="int64" value="36028797018963968"/>
|
|
||||||
</node>
|
|
||||||
</children>
|
|
||||||
</node>
|
|
||||||
</children>
|
|
||||||
</node>
|
|
||||||
</region>
|
|
||||||
</save>
|
</save>
|
||||||
|
|||||||
BIN
Mods/DnD 5.5e AIO Russian/mod_publish_logo.png
Normal file
BIN
Mods/DnD 5.5e AIO Russian/mod_publish_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
@@ -1,12 +1,11 @@
|
|||||||
# DnD 5.5e All-in-One BEYOND Russian Localization
|
# DnD 5.5e All-in-One BEYOND Russian Localization
|
||||||
|
|
||||||
Русский перевод мода **DnD 5.5e All-in-One BEYOND** для **Baldur's Gate 3**.
|
Русская локализация [**DnD 5.5e All-in-One BEYOND**](https://github.com/Yoonmoonsik/dnd55e) для **Baldur's Gate 3**.
|
||||||
|
|
||||||
Оригинальный репозиторий:
|
Публикация на [mod.io](https://mod.io/g/baldursgate3/m/dnd-55e-all-in-one-beyond-russian-localization).
|
||||||
[Yoonmoonsik/dnd55e](https://github.com/Yoonmoonsik/dnd55e)
|
|
||||||
|
|
||||||
## О моде
|
## О моде
|
||||||
|
|
||||||
**DnD 5.5e All-in-One BEYOND** переносит в Baldur's Gate 3 контент и механики, вдохновлённые Dungeons & Dragons 5.5e. Мод расширяет игру новыми и переработанными элементами, включая классы, предыстории, таланты, расы, заклинания и другие связанные системы.
|
Русский перевод мода **DnD 5.5e All-in-One BEYOND**, который переносит в **Baldur's Gate 3** широкий пласт контента и правил **D&D 5.5e / PHB 2024**.
|
||||||
|
|
||||||
Этот проект предназначен для русской локализации оригинального мода и его текстового контента.
|
Локализация поддерживается в темпе с апстримом, который сейчас развивается в сторону более полного охвата классов, рас, предысторий, фитов и заклинаний.
|
||||||
|
|||||||
862
glossary/glossary.normalized.json
Normal file
862
glossary/glossary.normalized.json
Normal file
@@ -0,0 +1,862 @@
|
|||||||
|
{
|
||||||
|
"AC": "КБ",
|
||||||
|
"Ability": "характеристика",
|
||||||
|
"Ability Check": "проверка характеристики",
|
||||||
|
"Ability Checks": "проверки характеристик",
|
||||||
|
"Acid Splash": "Всплеск кислоты",
|
||||||
|
"Acid splash": "Всплеск кислоты",
|
||||||
|
"Acolyte": "Послушник",
|
||||||
|
"Acrobatics": "Акробатика",
|
||||||
|
"Actor": "Артистичный",
|
||||||
|
"Advantage": "преимущество",
|
||||||
|
"Aid": "Помощь",
|
||||||
|
"Alarm": "Сигнал тревоги",
|
||||||
|
"Alert": "Бдительный",
|
||||||
|
"Alter self": "Смена обличья",
|
||||||
|
"Animal Friendship": "Дружба с животными",
|
||||||
|
"Animal Handling": "Дрессировка",
|
||||||
|
"Animal friendship": "Дружба с животными",
|
||||||
|
"Animal messenger": "Почтовое животное",
|
||||||
|
"Animal shapes": "Превращение в животных",
|
||||||
|
"Animate dead": "Восставший труп",
|
||||||
|
"Animate objects": "Оживление вещей",
|
||||||
|
"Antilife shell": "Преграда жизни",
|
||||||
|
"Antimagic field": "Преграда магии",
|
||||||
|
"Antipathy/sympathy": "Антипатия/симпатия",
|
||||||
|
"Arcana": "Магия",
|
||||||
|
"Arcane Vigor": "Мистическая бодрость",
|
||||||
|
"Arcane eye": "Магический глаз",
|
||||||
|
"Arcane gate": "Магические врата",
|
||||||
|
"Arcane lock": "Волшебный замок",
|
||||||
|
"Armor": "броня",
|
||||||
|
"Armor Class": "класс брони",
|
||||||
|
"Armor of Agathys": "Броня Агафиса",
|
||||||
|
"Armour": "броня",
|
||||||
|
"Armour Class": "класс брони",
|
||||||
|
"Arms of Hadar": "Руки Хадара",
|
||||||
|
"Astral projection": "Проекция в астрал",
|
||||||
|
"Athlete": "Атлетичный",
|
||||||
|
"Athletics": "Атлетика",
|
||||||
|
"Attack": "атака",
|
||||||
|
"Attack Action": "действие Атака",
|
||||||
|
"Attack Roll": "бросок атаки",
|
||||||
|
"Attack Rolls": "броски атаки",
|
||||||
|
"Augury": "Гадание",
|
||||||
|
"Aura of Vitality": "Аура жизненной силы",
|
||||||
|
"Aura of life": "Аура жизни",
|
||||||
|
"Aura of purity": "Аура очищения",
|
||||||
|
"Aura of vitality": "Аура живучести",
|
||||||
|
"Awaken": "Пробуждение разума",
|
||||||
|
"Background": "происхождение",
|
||||||
|
"Backgrounds": "происхождения",
|
||||||
|
"Bane": "Порча",
|
||||||
|
"Banishing smite": "Изгоняющая кара",
|
||||||
|
"Banishment": "Изгнание",
|
||||||
|
"Barbarian": "варвар",
|
||||||
|
"Bard": "бард",
|
||||||
|
"Bardic Inspiration": "Бардовское вдохновение",
|
||||||
|
"Bardic Inspiration die": "Заряд Бардовского вдохновения",
|
||||||
|
"Barkskin": "Дубовая кора",
|
||||||
|
"Battle Inspiration": "Боевое вдохновение",
|
||||||
|
"Battleaxe": "Боевой топор",
|
||||||
|
"Beacon of hope": "Маяк надежды",
|
||||||
|
"Beast sense": "Животные чувства",
|
||||||
|
"Beguiling Magic": "Чарующая магия",
|
||||||
|
"Bestow Curse": "Проклятие",
|
||||||
|
"Bestow Curse: Additional Damage": "Наложение проклятия: Дополнительный урон",
|
||||||
|
"Bestow Curse: Attack Disadvantage": "Наложение проклятия: Помеха атакам",
|
||||||
|
"Bestow Curse: Charisma Disadvantage": "Наложение проклятия: Помеха Харизме",
|
||||||
|
"Bestow Curse: Constitution Disadvantage": "Наложение проклятия: Помеха Телосложению",
|
||||||
|
"Bestow Curse: Dexterity Disadvantage": "Наложение проклятия: Помеха Ловкости",
|
||||||
|
"Bestow Curse: Dread": "Наложение проклятия: Ужас",
|
||||||
|
"Bestow Curse: Intelligence Disadvantage": "Наложение проклятия: Помеха Интеллекту",
|
||||||
|
"Bestow Curse: Strength Disadvantage": "Наложение проклятия: Помеха Силе",
|
||||||
|
"Bestow Curse: Wisdom Disadvantage": "Наложение проклятия: Помеха Мудрости",
|
||||||
|
"Bestow curse": "Проклятие",
|
||||||
|
"Bigby’s hand": "Длань Бигби",
|
||||||
|
"Blade Ward": "Оберег от оружия",
|
||||||
|
"Blade barrier": "Стена клинков",
|
||||||
|
"Blade ward": "Защита от оружия",
|
||||||
|
"Bless": "Благословение",
|
||||||
|
"Blessed Healer": "Благословенный целитель",
|
||||||
|
"Blessed Strikes": "Благословенные удары",
|
||||||
|
"Blight": "Усыхание",
|
||||||
|
"Blinded": "Ослеплённый",
|
||||||
|
"Blinding smite": "Ослепляющая кара",
|
||||||
|
"Blindness/deafness": "Глухота/слепота",
|
||||||
|
"Blink": "Мерцание",
|
||||||
|
"Blowgun": "Духовая трубка",
|
||||||
|
"Blur": "Размытый образ",
|
||||||
|
"Bonus Action": "бонусное действие",
|
||||||
|
"Bonus Unarmed Strike": "Бонусная безоружная атака",
|
||||||
|
"Booming Blade": "Громовой клинок",
|
||||||
|
"Branding smite": "Клеймящая кара",
|
||||||
|
"Brutal Strike": "Жестокий удар",
|
||||||
|
"Burning hands": "Огненные ладони",
|
||||||
|
"Bursting Sinew": "Взрыв сухожилий",
|
||||||
|
"Call lightning": "Призыв молнии",
|
||||||
|
"Calm emotions": "Умиротворение",
|
||||||
|
"Cantrip": "фокус",
|
||||||
|
"Cantrips": "фокусы",
|
||||||
|
"Chain lightning": "Пляшущая молния",
|
||||||
|
"Channel Divinity": "Направление божественной энергии",
|
||||||
|
"Charger": "Налётчик",
|
||||||
|
"Charisma": "Харизма",
|
||||||
|
"Charlatan": "Шарлатан",
|
||||||
|
"Charm Person": "Приворот гуманоида",
|
||||||
|
"Charm person": "Приворот гуманоида",
|
||||||
|
"Charmed": "Очарованный",
|
||||||
|
"Chill Touch": "Пробирающий до костей холод",
|
||||||
|
"Chill touch": "Леденящее прикосновение",
|
||||||
|
"Chromatic Orb": "Хроматический шар",
|
||||||
|
"Chromatic Orb: Acid": "Хроматический шар: кислота",
|
||||||
|
"Chromatic Orb: Cold": "Хроматический шар: холод",
|
||||||
|
"Chromatic Orb: Fire": "Хроматический шар: огонь",
|
||||||
|
"Chromatic Orb: Lightning": "Хроматический шар: молния",
|
||||||
|
"Chromatic Orb: Poison": "Хроматический шар: яд",
|
||||||
|
"Chromatic Orb: Thunder": "Хроматический шар: гром",
|
||||||
|
"Chromatic orb": "Цветной шарик",
|
||||||
|
"Circle Forms": "Облики круга",
|
||||||
|
"Circle of death": "Круг смерти",
|
||||||
|
"Circle of power": "Круг силы",
|
||||||
|
"Circle of the Moon Spells": "Заклинания круга Луны",
|
||||||
|
"Clairvoyance": "Подсматривание",
|
||||||
|
"Class": "класс",
|
||||||
|
"Cleave": "Рассечение",
|
||||||
|
"Cleric": "жрец",
|
||||||
|
"Clone": "Двойник",
|
||||||
|
"Cloud of Daggers": "Облако кинжалов",
|
||||||
|
"Cloud of daggers": "Облако кинжалов",
|
||||||
|
"Cloudkill": "Облако смерти",
|
||||||
|
"Club": "Дубинка",
|
||||||
|
"College of Lore": "Коллегия Знаний",
|
||||||
|
"College of Swords": "Коллегия Мечей",
|
||||||
|
"College of Valor": "Коллегия Отваги",
|
||||||
|
"College of Valour": "Коллегия Отваги",
|
||||||
|
"Color Spray": "Цветные брызги",
|
||||||
|
"Color spray": "Сверкающие брызги",
|
||||||
|
"Colour Spray": "Цветные брызги",
|
||||||
|
"Command": "Приказ",
|
||||||
|
"Commune": "Общение",
|
||||||
|
"Commune with nature": "Общение с природой",
|
||||||
|
"Compelled duel": "Вызов на дуэль",
|
||||||
|
"Comprehend languages": "Понимание языков",
|
||||||
|
"Compulsion": "Принуждение",
|
||||||
|
"Concentration": "концентрация",
|
||||||
|
"Condition": "Состояние",
|
||||||
|
"Cone of Cold": "Конус холода",
|
||||||
|
"Cone of cold": "Конус холода",
|
||||||
|
"Confusion": "Смятение",
|
||||||
|
"Conjure animals": "Призыв животных",
|
||||||
|
"Conjure barrage": "Призыв заграждения",
|
||||||
|
"Conjure celestial": "Призыв небожителя",
|
||||||
|
"Conjure elemental": "Призыв элементаля",
|
||||||
|
"Conjure fey": "Призыв феи",
|
||||||
|
"Conjure minor elementals": "Призыв малых элементалей",
|
||||||
|
"Conjure volley": "Призыв залпа",
|
||||||
|
"Conjure woodland beings": "Призыв лесных обитателей",
|
||||||
|
"Constitution": "Выносливость",
|
||||||
|
"Contact other plane": "Связь с иным миром",
|
||||||
|
"Contagion": "Заражение",
|
||||||
|
"Contagion: Blinding Sickness": "Заражение: Ослепляющая болезнь",
|
||||||
|
"Contagion: Filth Fever": "Заражение: Грязная лихорадка",
|
||||||
|
"Contagion: Flesh Rot": "Заражение: Гниль плоти",
|
||||||
|
"Contagion: Mindfire": "Заражение: Огонь разума",
|
||||||
|
"Contagion: Seizure": "Заражение: Припадок",
|
||||||
|
"Contagion: Slimy Doom": "Заражение: Склизкая гибель",
|
||||||
|
"Contingency": "Предосторожность",
|
||||||
|
"Continual flame": "Вечный огонь",
|
||||||
|
"Control water": "Власть над водами",
|
||||||
|
"Control weather": "Власть над погодой",
|
||||||
|
"Cordon of arrows": "Завеса стрел",
|
||||||
|
"Countercharm": "Контрчары",
|
||||||
|
"Counterspell": "Контрзаклинание",
|
||||||
|
"Cover": "укрытие",
|
||||||
|
"Create Water": "Создание воды",
|
||||||
|
"Create food and water": "Сотворение пищи и воды",
|
||||||
|
"Create or destroy water": "Сотворение или уничтожение воды",
|
||||||
|
"Create undead": "Сотворение нежити",
|
||||||
|
"Creation": "Сотворение",
|
||||||
|
"Creature": "Существо",
|
||||||
|
"Criminal": "Преступник",
|
||||||
|
"Crossbow Expert": "Эксперт в арбалетах",
|
||||||
|
"Crossbow, hand": "Арбалет, ручной",
|
||||||
|
"Crossbow, heavy": "Арбалет, тяжёлый",
|
||||||
|
"Crossbow, light": "Арбалет, лёгкий",
|
||||||
|
"Crown of madness": "Корона безумия",
|
||||||
|
"Crusader's mantle": "Мантия крестоносца",
|
||||||
|
"Cure Wounds": "Исцеление ран",
|
||||||
|
"Cure wounds": "Лечение ран",
|
||||||
|
"Cutting Words": "Острые слова",
|
||||||
|
"D20 Test": "проверка к20",
|
||||||
|
"Dagger": "Кинжал",
|
||||||
|
"Damage": "урон",
|
||||||
|
"Damage Roll": "бросок урона",
|
||||||
|
"Dancing Lights": "Пляшущие огоньки",
|
||||||
|
"Dancing lights": "Пляшущие огоньки",
|
||||||
|
"Danger Sense": "Чувство опасности",
|
||||||
|
"Darkness": "Тьма",
|
||||||
|
"Darkvision": "Ночное зрение",
|
||||||
|
"Dart": "Дротик",
|
||||||
|
"Dash": "Рывок",
|
||||||
|
"Daylight": "Дневной свет",
|
||||||
|
"Deafened": "Оглохший",
|
||||||
|
"Death Saving Throw": "спасбросок от смерти",
|
||||||
|
"Death Saving Throws": "спасброски от смерти",
|
||||||
|
"Death ward": "Защита от смерти",
|
||||||
|
"Deception": "Обман",
|
||||||
|
"Defensive Duelist": "Оборонительный дуэлянт",
|
||||||
|
"Deflect Attacks": "Отклонение атак",
|
||||||
|
"Delayed blast fireball": "Замедленный огненный шар",
|
||||||
|
"Demiplane": "Демиплан",
|
||||||
|
"Destructive wave": "Разрушительная волна",
|
||||||
|
"Detect evil and good": "Обнаружение добра и зла",
|
||||||
|
"Detect magic": "Обнаружение магии",
|
||||||
|
"Detect poison and disease": "Обнаружение болезней и яда",
|
||||||
|
"Detect thoughts": "Обнаружение мыслей",
|
||||||
|
"Dexterity": "Ловкость",
|
||||||
|
"Difficulty Class": "сложность",
|
||||||
|
"Dimension door": "Переносящая дверь",
|
||||||
|
"Disadvantage": "помеха",
|
||||||
|
"Disciple of Life": "Ученик жизни",
|
||||||
|
"Disengage": "Отступление",
|
||||||
|
"Disguise self": "Маскировка",
|
||||||
|
"Disintegrate": "Распад",
|
||||||
|
"Dispel evil and good": "Рассеивание добра и зла",
|
||||||
|
"Dispel magic": "Рассеивание магии",
|
||||||
|
"Dissonant Whispers": "Диссонирующий шепот",
|
||||||
|
"Dissonant whisper": "Диссонирующий шёпот",
|
||||||
|
"Divination": "Предсказание",
|
||||||
|
"Divine Order": "Божественный приказ",
|
||||||
|
"Divine Spark": "Божественная искра",
|
||||||
|
"Divine favor": "Божественное благоволение",
|
||||||
|
"Divine word": "Божественное слово",
|
||||||
|
"Dodge": "Уклонение",
|
||||||
|
"Dominate beast": "Подчинение зверя",
|
||||||
|
"Dominate monster": "Подчинение чудовища",
|
||||||
|
"Dominate person": "Подчинение личности",
|
||||||
|
"Dragonborn": "драконорождённый",
|
||||||
|
"Drawmij’s instant summons": "Дромиджево появление",
|
||||||
|
"Dream": "Вещий сон",
|
||||||
|
"Druid": "друид",
|
||||||
|
"Druidcraft": "Искусство друидов",
|
||||||
|
"Druidic": "Друидический",
|
||||||
|
"Dual Wielder": "Двойное оружие",
|
||||||
|
"Dungeon Delver": "Исследователь подземелий",
|
||||||
|
"Durable": "Стойкий",
|
||||||
|
"Dwarf": "дварф",
|
||||||
|
"Earthquake": "Землетрясение",
|
||||||
|
"Eldritch Blast": "Потусторонний разряд",
|
||||||
|
"Eldritch Hex": "Потусторонний сглаз",
|
||||||
|
"Eldritch Hexed: Charisma": "Потусторонний сглаз: Харизма",
|
||||||
|
"Eldritch Hexed: Constitution": "Потусторонний сглаз: Телосложение",
|
||||||
|
"Eldritch Hexed: Dexterity": "Потусторонний сглаз: Ловкость",
|
||||||
|
"Eldritch Hexed: Intelligence": "Потусторонний сглаз: Интеллект",
|
||||||
|
"Eldritch Hexed: Strength": "Потусторонний сглаз: Сила",
|
||||||
|
"Eldritch Hexed: Wisdom": "Потусторонний сглаз: Мудрость",
|
||||||
|
"Eldritch Strike": "Мистический удар",
|
||||||
|
"Eldritch blast": "Мистический заряд",
|
||||||
|
"Elemental Adept": "Стихийный адепт",
|
||||||
|
"Elemental Fury": "Стихийная ярость",
|
||||||
|
"Elemental weapon": "Стихийное оружие",
|
||||||
|
"Elf": "эльф",
|
||||||
|
"Empowered Strikes": "Усиленные удары",
|
||||||
|
"Enemy": "Враг",
|
||||||
|
"Enhance Ability": "Усиление характеристики",
|
||||||
|
"Enhance ability": "Улучшение характеристики",
|
||||||
|
"Enlarge/reduce": "Увеличение/уменьшение",
|
||||||
|
"Ensnaring strike": "Опутывающий удар",
|
||||||
|
"Entangle": "Опутывание",
|
||||||
|
"Entertainer": "Артист",
|
||||||
|
"Enthrall": "Речь златоуста",
|
||||||
|
"Etherealness": "Эфирность",
|
||||||
|
"Evard's black tentacles": "Эвардовы чёрные щупальца",
|
||||||
|
"Evasion": "Ускользание",
|
||||||
|
"Exhaustion": "истощение",
|
||||||
|
"Expeditious retreat": "Поспешное отступление",
|
||||||
|
"Expertise": "Мастерство",
|
||||||
|
"Extra Attack": "Дополнительная атака",
|
||||||
|
"Eyebite": "Разящее око",
|
||||||
|
"Fabricate": "Изготовление",
|
||||||
|
"Faerie Fire": "Фейское сияние",
|
||||||
|
"Faerie fire": "Огонь фей",
|
||||||
|
"Faithful Steed": "Верный скакун",
|
||||||
|
"False Life": "Ложная жизнь",
|
||||||
|
"False life": "Ложная жизнь",
|
||||||
|
"Fear": "Ужас",
|
||||||
|
"Feat": "черта",
|
||||||
|
"Feat: Fade Away": "Черта: Исчезновение",
|
||||||
|
"Feat: Fey Touched": "Черта: Затронутый феями",
|
||||||
|
"Feather Fall": "Плавное падение",
|
||||||
|
"Feather fall": "Падение пёрышком",
|
||||||
|
"Feats": "черты",
|
||||||
|
"Feature": "особенность",
|
||||||
|
"Features": "особенности",
|
||||||
|
"Feeblemind": "Слабоумие",
|
||||||
|
"Feign death": "Притворная смерть",
|
||||||
|
"Fighter": "воин",
|
||||||
|
"Find Familiar": "Призыв фамильяра",
|
||||||
|
"Find Steed": "Обретение скакуна",
|
||||||
|
"Find familiar": "Поиск фамильяра",
|
||||||
|
"Find steed": "Поиск скакуна",
|
||||||
|
"Find the path": "Поиск пути",
|
||||||
|
"Find traps": "Поиск ловушек",
|
||||||
|
"Finesse": "фехтовальное",
|
||||||
|
"Finger of death": "Перст смерти",
|
||||||
|
"Fire Bolt": "Огненный снаряд",
|
||||||
|
"Fire bolt": "Огненный снаряд",
|
||||||
|
"Fire shield": "Огненный щит",
|
||||||
|
"Fire storm": "Огненная буря",
|
||||||
|
"Fireball": "Огненный шар",
|
||||||
|
"Flail": "Цеп",
|
||||||
|
"Flame Blade": "Горящий клинок",
|
||||||
|
"Flame blade": "Горящий клинок",
|
||||||
|
"Flame strike": "Небесный огонь",
|
||||||
|
"Flaming Sphere": "Пылающая сфера",
|
||||||
|
"Flaming sphere": "Пылающий шар",
|
||||||
|
"Flesh to stone": "Окаменение",
|
||||||
|
"Fly": "Полёт",
|
||||||
|
"Fog Cloud": "Облако тумана",
|
||||||
|
"Fog cloud": "Туманное облако",
|
||||||
|
"Folk Hero": "Народный герой",
|
||||||
|
"Font of Inspiration": "Источник вдохновения",
|
||||||
|
"Forbiddance": "Запрет",
|
||||||
|
"Forcecage": "Узилище",
|
||||||
|
"Forceful Blow": "Мощный удар",
|
||||||
|
"Foresight": "Предвидение",
|
||||||
|
"Fount of Moonlight": "Источник лунного света",
|
||||||
|
"Freedom of movement": "Свобода перемещения",
|
||||||
|
"Frenzy": "Неистовство",
|
||||||
|
"Friends": "Дружба",
|
||||||
|
"Frightened": "Испуганный",
|
||||||
|
"Gaseous form": "Газообразная форма",
|
||||||
|
"Gate": "Врата",
|
||||||
|
"Geas": "Обет",
|
||||||
|
"Gentle repose": "Нетленные останки",
|
||||||
|
"Giant insect": "Гигантское насекомое",
|
||||||
|
"Glaive": "Глефа",
|
||||||
|
"Glibness": "Находчивость",
|
||||||
|
"Globe of invulnerability": "Сфера неуязвимости",
|
||||||
|
"Glyph of warding": "Охранные руны",
|
||||||
|
"Gnome": "гном",
|
||||||
|
"Goodberry": "Вкусные ягоды",
|
||||||
|
"Grapple": "захват",
|
||||||
|
"Grappled": "Схваченный",
|
||||||
|
"Grappler": "Борец",
|
||||||
|
"Grasping vine": "Цепкая лоза",
|
||||||
|
"Graze": "Задевание",
|
||||||
|
"Grease": "Скольжение",
|
||||||
|
"Great Weapon Master": "Мастер большого оружия",
|
||||||
|
"Greataxe": "Секира",
|
||||||
|
"Greatclub": "Палица",
|
||||||
|
"Greater invisibility": "Высшая невидимость",
|
||||||
|
"Greater restoration": "Высшее восстановление",
|
||||||
|
"Greatsword": "Двуручный меч",
|
||||||
|
"Green-Flame Blade": "Клинок зелёного пламени",
|
||||||
|
"Guardian of faith": "Страж веры",
|
||||||
|
"Guards and wards": "Стражи",
|
||||||
|
"Guidance": "Наставление",
|
||||||
|
"Guiding Bolt": "Направляющий луч",
|
||||||
|
"Guiding bolt": "Направленный снаряд",
|
||||||
|
"Guild Artisan": "Гильдейский ремесленник",
|
||||||
|
"Gust of Wind": "Порыв ветра",
|
||||||
|
"Gust of wind": "Порыв ветра",
|
||||||
|
"HP": "ОЗ",
|
||||||
|
"Hail of Thorns": "Град шипов",
|
||||||
|
"Hail of thorns": "Град шипов",
|
||||||
|
"Halberd": "Алебарда",
|
||||||
|
"Half-Elf": "полуэльф",
|
||||||
|
"Half-Orc": "полуорк",
|
||||||
|
"Halfling": "полурослик",
|
||||||
|
"Hallow": "Святилище",
|
||||||
|
"Hallucinatory terrain": "Мираж",
|
||||||
|
"Hamstring Blow": "Удар по сухожилиям",
|
||||||
|
"Handaxe": "Ручной топор",
|
||||||
|
"Harm": "Поражение",
|
||||||
|
"Haste": "Ускорение",
|
||||||
|
"Heal": "Полное исцеление",
|
||||||
|
"Healer": "Лекарь",
|
||||||
|
"Healing": "лечение",
|
||||||
|
"Healing Word": "Исцеляющее слово",
|
||||||
|
"Healing word": "Лечащее слово",
|
||||||
|
"Heat metal": "Раскалённый металл",
|
||||||
|
"Heavily Armored": "Знаток тяжёлых доспехов",
|
||||||
|
"Heavy": "тяжёлое",
|
||||||
|
"Heavy Armor": "тяжёлые доспехи",
|
||||||
|
"Heavy Armor Master": "Мастер тяжёлых доспехов",
|
||||||
|
"Heightened Focus": "Обострённый фокус",
|
||||||
|
"Hellish rebuke": "Адское возмездие",
|
||||||
|
"Help": "Помощь",
|
||||||
|
"Heroes’ feast": "Пир героев",
|
||||||
|
"Heroic Inspiration": "Героическое вдохновение",
|
||||||
|
"Heroic Warrior": "Героический воин",
|
||||||
|
"Heroism": "Героизм",
|
||||||
|
"Hex": "Сглаз",
|
||||||
|
"Hex (Charisma)": "Сглаз (Харизма)",
|
||||||
|
"Hex (Constitution)": "Сглаз (Телосложение)",
|
||||||
|
"Hex (Dexterity)": "Сглаз (Ловкость)",
|
||||||
|
"Hex (Intelligence)": "Сглаз (Интеллект)",
|
||||||
|
"Hex (Strength)": "Сглаз (Сила)",
|
||||||
|
"Hex (Wisdom)": "Сглаз (Мудрость)",
|
||||||
|
"Hide": "Спрятаться",
|
||||||
|
"History": "История",
|
||||||
|
"Hit Dice": "кости хитов",
|
||||||
|
"Hit Die": "кость хитов",
|
||||||
|
"Hit Point": "ОЗ",
|
||||||
|
"Hit Points": "ОЗ",
|
||||||
|
"Hold Person": "Паралич гуманоида",
|
||||||
|
"Hold monster": "Удержание чудовища",
|
||||||
|
"Hold person": "Удержание личности",
|
||||||
|
"Holy aura": "Аура святости",
|
||||||
|
"Human": "человек",
|
||||||
|
"Hunger of Hadar": "Голод Хадара",
|
||||||
|
"Hunter's Mark": "Метка охотника",
|
||||||
|
"Hunter's mark": "Метка охотника",
|
||||||
|
"Hypnotic pattern": "Гипнотический узор",
|
||||||
|
"Ice Knife": "Ледяной нож",
|
||||||
|
"Ice storm": "Град",
|
||||||
|
"Identify": "Опознание",
|
||||||
|
"Illusory script": "Невидимое письмо",
|
||||||
|
"Imprisonment": "Заточение",
|
||||||
|
"Improved Combat Superiority": "Усовершенствованное превосходство в бою",
|
||||||
|
"Improved Warding Flare": "Улучшенная защитная вспышка",
|
||||||
|
"Incapacitated": "Недееспособный",
|
||||||
|
"Incendiary cloud": "Воспламеняющаяся туча",
|
||||||
|
"Indomitable": "Несгибаемая воля",
|
||||||
|
"Inflict Wounds": "Нанесение ран",
|
||||||
|
"Inflict wounds": "Нанесение ран",
|
||||||
|
"Initiative": "инициатива",
|
||||||
|
"Insect plague": "Нашествие насекомых",
|
||||||
|
"Insight": "Проницательность",
|
||||||
|
"Inspiring Leader": "Воодушевляющий лидер",
|
||||||
|
"Instinctive Pounce": "Интуитивный рывок",
|
||||||
|
"Intelligence": "Интеллект",
|
||||||
|
"Intimidation": "Запугивание",
|
||||||
|
"Investigation": "Расследование",
|
||||||
|
"Invisibility": "Невидимость",
|
||||||
|
"Invisible": "Невидимый",
|
||||||
|
"Invoke Duplicity": "Призыв двойника",
|
||||||
|
"Jack of All Trades": "Мастер на все руки",
|
||||||
|
"Jack-of-all-Trades": "Мастер на все руки",
|
||||||
|
"Javelin": "Метательное копьё",
|
||||||
|
"Jump": "Прыжок",
|
||||||
|
"Keen Mind": "Отличная память",
|
||||||
|
"Knock": "Стук",
|
||||||
|
"Lance": "Длинное копьё",
|
||||||
|
"Legend lore": "Знание легенд",
|
||||||
|
"Leomund’s secret chest": "Леомундов потайной сундук",
|
||||||
|
"Leomund’s tiny hut": "Леомундова хижина",
|
||||||
|
"Lesser Restoration": "Низшее восстановление",
|
||||||
|
"Lesser restoration": "Малое восстановление",
|
||||||
|
"Level": "уровень",
|
||||||
|
"Levitate": "Левитация",
|
||||||
|
"Light": "Свет",
|
||||||
|
"Light hammer": "Лёгкий молот",
|
||||||
|
"Lightly Armored": "Знаток лёгких доспехов",
|
||||||
|
"Lightning arrow": "Молниевая стрела",
|
||||||
|
"Lightning bolt": "Молния",
|
||||||
|
"Linguist": "Языковед",
|
||||||
|
"Locate animals or plants": "Поиск животных или растений",
|
||||||
|
"Locate creature": "Поиск существа",
|
||||||
|
"Locate object": "Поиск предмета",
|
||||||
|
"Long Rest": "Долгий отдых",
|
||||||
|
"Longbow": "Длинный лук",
|
||||||
|
"Longstrider": "Скороход",
|
||||||
|
"Longsword": "Длинный меч",
|
||||||
|
"Lucky": "Везунчик",
|
||||||
|
"Mace": "Булава",
|
||||||
|
"Mage Armor": "Магический доспех",
|
||||||
|
"Mage Armour": "Магический доспех",
|
||||||
|
"Mage Hand": "Магическая рука",
|
||||||
|
"Mage Slayer": "Убийца магов",
|
||||||
|
"Mage armor": "Доспехи мага",
|
||||||
|
"Mage hand": "Магическая рука",
|
||||||
|
"Magic Action": "действие Магия",
|
||||||
|
"Magic Initiate": "Посвящённый в магию",
|
||||||
|
"Magic Initiate (Cleric)": "Посвященный в магию: жрец",
|
||||||
|
"Magic Initiate (Sorcerer)": "Посвященный в магию: чародей",
|
||||||
|
"Magic Initiate (Warlock)": "Посвященный в магию: колдун",
|
||||||
|
"Magic Initiate (Wizard)": "Посвященный в магию: волшебник",
|
||||||
|
"Magic Missile": "Волшебная стрела",
|
||||||
|
"Magic Weapon": "Волшебное оружие",
|
||||||
|
"Magic action": "действие Магия",
|
||||||
|
"Magic circle": "Магический круг",
|
||||||
|
"Magic jar": "Волшебный сосуд",
|
||||||
|
"Magic missile": "Волшебная стрела",
|
||||||
|
"Magic mouth": "Волшебные уста",
|
||||||
|
"Magic weapon": "Магическое оружие",
|
||||||
|
"Magician": "Маг",
|
||||||
|
"Major image": "Образ",
|
||||||
|
"Mantle of Inspiration": "Мантия вдохновения",
|
||||||
|
"Martial Adept": "Воинский адепт",
|
||||||
|
"Martial Arts": "Боевые искусства",
|
||||||
|
"Martial weapons": "воинское оружие",
|
||||||
|
"Mass Healing Word": "Массовое исцеляющее слово",
|
||||||
|
"Mass cure wounds": "Множественное лечение ран",
|
||||||
|
"Mass heal": "Множественное полное исцеление",
|
||||||
|
"Mass healing word": "Множественное лечащее слово",
|
||||||
|
"Mass suggestion": "Множественное внушение",
|
||||||
|
"Mastery Properties": "свойства мастерства",
|
||||||
|
"Mastery Properties: Cleave": "Свойства мастерства: Рассечение",
|
||||||
|
"Mastery Properties: Graze": "Свойства мастерства: Скользящий удар",
|
||||||
|
"Mastery Properties: Nick": "Свойства мастерства: Надрез",
|
||||||
|
"Mastery Properties: Push": "Свойства мастерства: Толчок",
|
||||||
|
"Mastery Properties: Sap": "Свойства мастерства: Истощение",
|
||||||
|
"Mastery Properties: Slow": "Свойства мастерства: Замедление",
|
||||||
|
"Mastery Properties: Topple": "Свойства мастерства: Опрокидывание",
|
||||||
|
"Mastery Properties: Vex": "Свойства мастерства: Отвлечение",
|
||||||
|
"Maul": "Молот",
|
||||||
|
"Maze": "Лабиринт",
|
||||||
|
"Medicine": "Медицина",
|
||||||
|
"Medium Armor": "средние доспехи",
|
||||||
|
"Medium Armor Master": "Мастер средних доспехов",
|
||||||
|
"Meld into stone": "Слияние с камнем",
|
||||||
|
"Melee Attack": "рукопашная атака",
|
||||||
|
"Melf's Acid Arrow": "Кислотная стрела Мелфа",
|
||||||
|
"Melf's acid arrow": "Кислотная стрела Мелфа",
|
||||||
|
"Mending": "Починка",
|
||||||
|
"Message": "Сообщение",
|
||||||
|
"Meteor swarm": "Метеоритный дождь",
|
||||||
|
"Mind Magic": "Магия разума",
|
||||||
|
"Mind Sliver": "Расщепление разума",
|
||||||
|
"Mind Spike": "Пронзание разума",
|
||||||
|
"Mind blank": "Сокрытие разума",
|
||||||
|
"Mindless Rage": "Безрассудная ярость",
|
||||||
|
"Minor Illusion": "Малая иллюзия",
|
||||||
|
"Minor illusion": "Малая иллюзия",
|
||||||
|
"Mirage arcane": "Таинственный мираж",
|
||||||
|
"Mirror Image": "Зеркальное отражение",
|
||||||
|
"Mirror image": "Отражения",
|
||||||
|
"Mislead": "Фальшивый двойник",
|
||||||
|
"Misty step": "Туманный шаг",
|
||||||
|
"Mobile": "Подвижный",
|
||||||
|
"Moderately Armored": "Знаток средних доспехов",
|
||||||
|
"Modify memory": "Изменение памяти",
|
||||||
|
"Monk": "монах",
|
||||||
|
"Moonbeam": "Лунный свет",
|
||||||
|
"Moonlight Step": "Лунный шаг",
|
||||||
|
"Moonlight Step Charge": "заряд «Лунного шага»",
|
||||||
|
"Mordenkainen’s faithful hound": "Верный пёс Морденкайнена",
|
||||||
|
"Mordenkainen’s magnificent mansion": "Великолепный особняк Морденкайнена",
|
||||||
|
"Mordenkainen’s private sanctum": "Кабинет Морденкайнена",
|
||||||
|
"Mordenkainen’s sword": "Меч Морденкайнена",
|
||||||
|
"Morningstar": "Моргенштерн",
|
||||||
|
"Mounted Combatant": "Верховой боец",
|
||||||
|
"Move earth": "Движение почвы",
|
||||||
|
"Nature": "Природа",
|
||||||
|
"Necrotic Damage": "урон некротической энергией",
|
||||||
|
"Net": "Сеть",
|
||||||
|
"Nick": "Надрез",
|
||||||
|
"Noble": "Дворянин",
|
||||||
|
"Nondetection": "Необнаружимость",
|
||||||
|
"Nystul’s magic aura": "Нистулова ложная аура",
|
||||||
|
"Observant": "Внимательный",
|
||||||
|
"Opportunity Attack": "внеочередная атака",
|
||||||
|
"Opportunity Attacks": "внеочередные атаки",
|
||||||
|
"Origin Feat: Alert": "Черта происхождения: Бдительный",
|
||||||
|
"Origin Feat: Magic Initiate (Cleric)": "Черта происхождения: Посвященный в магию (Жрец)",
|
||||||
|
"Origin Feat: Magic Initiate (Wizard)": "Черта происхождения: Посвященный в магию (Волшебник)",
|
||||||
|
"Origin Feat: Magic Initiate (Druid)": "Черта происхождения: Посвященный в магию (Друид)",
|
||||||
|
"Origin Feat: Healer": "Черта происхождения: Лекарь",
|
||||||
|
"Origin Feat: Lucky": "Черта происхождения: Везунчик",
|
||||||
|
"Origin Feat: Tavern Brawler": "Черта происхождения: Драчун",
|
||||||
|
"Origin Feat: Musician": "Черта происхождения: Музыкант",
|
||||||
|
"Origin Feat: Savage Attacker": "Черта происхождения: Дикий атакующий",
|
||||||
|
"Origin Feat: Skilled": "Черта происхождения: Одаренный",
|
||||||
|
"Origin Feat: Tough": "Черта происхождения: Крепкий",
|
||||||
|
"Otiluke’s freezing sphere": "Отилюков ледяной шар",
|
||||||
|
"Otiluke’s resilient sphere": "Отилюков упругий шар",
|
||||||
|
"Otto’s irresistible dance": "Неудержимая пляска Отто",
|
||||||
|
"Outlander": "Чужеземец",
|
||||||
|
"Paladin": "паладин",
|
||||||
|
"Paralyzed": "Парализованный",
|
||||||
|
"Pass without Trace": "Бесследный шаг",
|
||||||
|
"Pass without trace": "Бесследное передвижение",
|
||||||
|
"Passwall": "Создание прохода",
|
||||||
|
"Patient Defense": "Терпеливая оборона",
|
||||||
|
"Perception": "Внимание",
|
||||||
|
"Performance": "Исполнение",
|
||||||
|
"Persuasion": "Убеждение",
|
||||||
|
"Petrified": "Окаменевший",
|
||||||
|
"Phantasmal Force": "Сила фантазма",
|
||||||
|
"Phantasmal force": "Воображаемая сила",
|
||||||
|
"Phantasmal killer": "Воображаемый убийца",
|
||||||
|
"Phantom Steed": "Призрачный скакун",
|
||||||
|
"Phantom steed": "Призрачный скакун",
|
||||||
|
"Pike": "Пика",
|
||||||
|
"Planar ally": "Планарный союзник",
|
||||||
|
"Planar binding": "Планарные узы",
|
||||||
|
"Plane shift": "Уход в иной мир",
|
||||||
|
"Plant growth": "Рост растений",
|
||||||
|
"Poison Spray": "Брызги яда",
|
||||||
|
"Poison spray": "Ядовитые брызги",
|
||||||
|
"Poisoned": "Отравленный",
|
||||||
|
"Polearm Master": "Мастер древкового оружия",
|
||||||
|
"Polymorph": "Превращение",
|
||||||
|
"Potent Spellcasting": "Мощное заклинание",
|
||||||
|
"Power word heal": "Слово Силы: исцеление",
|
||||||
|
"Power word kill": "Слово Силы: смерть",
|
||||||
|
"Power word stun": "Слово Силы: оглушение",
|
||||||
|
"Prayer of Healing": "Исцеляющий молебен",
|
||||||
|
"Prayer of healing": "Молебен лечения",
|
||||||
|
"Preserve Life": "Сохранение жизни",
|
||||||
|
"Prestidigitation": "Фокусы",
|
||||||
|
"Primal Knowledge": "Первозданное знание",
|
||||||
|
"Primal Order": "Изначальный приказ",
|
||||||
|
"Primal Strike": "Первобытный удар",
|
||||||
|
"Prismatic spray": "Радужные брызги",
|
||||||
|
"Prismatic wall": "Радужная стена",
|
||||||
|
"Produce Flame": "Создание пламени",
|
||||||
|
"Produce flame": "Сотворение пламени",
|
||||||
|
"Proficiency": "умение",
|
||||||
|
"Proficiency Bonus": "бонус умения",
|
||||||
|
"Proficient": "владение",
|
||||||
|
"Programmed illusion": "Заданная иллюзия",
|
||||||
|
"Project image": "Проекция",
|
||||||
|
"Prone": "Сбитый с ног",
|
||||||
|
"Protection from Evil and Good": "Защита от зла и добра",
|
||||||
|
"Protection from energy": "Защита от энергии",
|
||||||
|
"Protection from evil and good": "Защита от добра и зла",
|
||||||
|
"Protection from poison": "Защита от яда",
|
||||||
|
"Protector": "Защитник",
|
||||||
|
"Purify food and drink": "Очищение пищи и питья",
|
||||||
|
"Purity of Body": "Чистота тела",
|
||||||
|
"Push": "Отталкивание",
|
||||||
|
"Quarterstaff": "Боевой посох",
|
||||||
|
"Radiant Damage": "урон излучением",
|
||||||
|
"Rage": "Ярость",
|
||||||
|
"Raise dead": "Оживление",
|
||||||
|
"Ranged Attack": "дальнобойная атака",
|
||||||
|
"Ranger": "следопыт",
|
||||||
|
"Rapier": "Рапира",
|
||||||
|
"Rary’s telepathic bond": "Ментальная связь Рэри",
|
||||||
|
"Ray of Sickness": "Луч болезни",
|
||||||
|
"Ray of enfeeblement": "Луч слабости",
|
||||||
|
"Ray of frost": "Луч холода",
|
||||||
|
"Ray of sickness": "Луч болезни",
|
||||||
|
"Reaction": "реакция",
|
||||||
|
"Reapply Hex": "Повторно наложить Сглаз",
|
||||||
|
"Reapply Hex (Charisma)": "Повторно наложить Сглаз (Харизма)",
|
||||||
|
"Reapply Hex (Constitution)": "Повторно наложить Сглаз (Телосложение)",
|
||||||
|
"Reapply Hex (Dexterity)": "Повторно наложить Сглаз (Ловкость)",
|
||||||
|
"Reapply Hex (Intelligence)": "Повторно наложить Сглаз (Интеллект)",
|
||||||
|
"Reapply Hex (Strength)": "Повторно наложить Сглаз (Сила)",
|
||||||
|
"Reapply Hex (Wisdom)": "Повторно наложить Сглаз (Мудрость)",
|
||||||
|
"Reckless Attack": "Безрассудная атака",
|
||||||
|
"Redirect Attacks": "Перенаправление атак",
|
||||||
|
"Regenerate": "Регенерация",
|
||||||
|
"Reincarnate": "Реинкарнация",
|
||||||
|
"Relentless Rage": "Неудержимая ярость",
|
||||||
|
"Religion": "Религия",
|
||||||
|
"Remarkable Athlete": "Выдающийся атлет",
|
||||||
|
"Remove Curse": "Снятие проклятия",
|
||||||
|
"Remove curse": "Снятие проклятья",
|
||||||
|
"Resilient": "Устойчивый",
|
||||||
|
"Resistance": "Сопротивление",
|
||||||
|
"Restrained": "Опутанный",
|
||||||
|
"Resurrection": "Воскрешение",
|
||||||
|
"Retaliation": "Возмездие",
|
||||||
|
"Reverse gravity": "Изменение тяготения",
|
||||||
|
"Revivify": "Возрождение",
|
||||||
|
"Ritual Caster": "Ритуальный заклинатель",
|
||||||
|
"Rogue": "плут",
|
||||||
|
"Rope trick": "Трюк с верёвкой",
|
||||||
|
"Rule: One Spell with a Spell Slot per Turn": "Правило: одно заклинание с ячейкой за ход",
|
||||||
|
"Sacred Flame": "Священное пламя",
|
||||||
|
"Sacred flame": "Священное пламя",
|
||||||
|
"Sage": "Мудрец",
|
||||||
|
"Sanctuary": "Убежище",
|
||||||
|
"Sap": "Утомление",
|
||||||
|
"Savage Attacker": "Дикий атакующий",
|
||||||
|
"Saving Throw": "испытание",
|
||||||
|
"Saving Throw DC": "КС испытания",
|
||||||
|
"Saving Throws": "испытания",
|
||||||
|
"Scimitar": "Скимитар",
|
||||||
|
"Scorching Ray": "Опаляющий луч",
|
||||||
|
"Scorching ray": "Палящий луч",
|
||||||
|
"Scrying": "Наблюдение",
|
||||||
|
"Sear Undead": "Выжигание нежити",
|
||||||
|
"Searing smite": "Палящая кара",
|
||||||
|
"Second Wind": "Второе дыхание",
|
||||||
|
"See Invisibility": "Видение невидимого",
|
||||||
|
"See invisibility": "Видение невидимого",
|
||||||
|
"Seeming": "Притворство",
|
||||||
|
"Self-Restoration": "Самоисцеление",
|
||||||
|
"Sending": "Послание",
|
||||||
|
"Sentinel": "Страж",
|
||||||
|
"Sequester": "Изоляция",
|
||||||
|
"Shapechange": "Полное превращение",
|
||||||
|
"Sharpshooter": "Меткий стрелок",
|
||||||
|
"Shatter": "Разбивающий звук",
|
||||||
|
"Shield": "Щит",
|
||||||
|
"Shield Master": "Мастер щитов",
|
||||||
|
"Shield of Faith": "Щит веры",
|
||||||
|
"Shield of faith": "Щит веры",
|
||||||
|
"Shillelagh": "Шиллейла",
|
||||||
|
"Shocking Grasp": "Шоковое прикосновение",
|
||||||
|
"Shocking grasp": "Электрошок",
|
||||||
|
"Short Rest": "Короткий отдых",
|
||||||
|
"Shortbow": "Короткий лук",
|
||||||
|
"Shortsword": "Короткий меч",
|
||||||
|
"Sickle": "Серп",
|
||||||
|
"Silence": "Тишина",
|
||||||
|
"Silent image": "Безмолвный образ",
|
||||||
|
"Simulacrum": "Подобие",
|
||||||
|
"Skilled": "Одаренный",
|
||||||
|
"Skilled: Acrobatics": "Одаренный: Акробатика",
|
||||||
|
"Skilled: Animal Handling": "Одаренный: Уход за животными",
|
||||||
|
"Skilled: Arcana": "Одаренный: Магия",
|
||||||
|
"Skilled: Athletics": "Одаренный: Атлетика",
|
||||||
|
"Skilled: Deception": "Одаренный: Обман",
|
||||||
|
"Skilled: History": "Одаренный: История",
|
||||||
|
"Skilled: Insight": "Одаренный: Проницательность",
|
||||||
|
"Skilled: Intimidation": "Одаренный: Запугивание",
|
||||||
|
"Skilled: Investigation": "Одаренный: Расследование",
|
||||||
|
"Skilled: Medicine": "Одаренный: Медицина",
|
||||||
|
"Skilled: Nature": "Одаренный: Природа",
|
||||||
|
"Skilled: Perception": "Одаренный: Восприятие",
|
||||||
|
"Skilled: Performance": "Одаренный: Выступление",
|
||||||
|
"Skilled: Persuasion": "Одаренный: Убеждение",
|
||||||
|
"Skilled: Religion": "Одаренный: Религия",
|
||||||
|
"Skilled: Sleight of Hand": "Одаренный: Ловкость рук",
|
||||||
|
"Skilled: Stealth": "Одаренный: Скрытность",
|
||||||
|
"Skilled: Survival": "Одаренный: Выживание",
|
||||||
|
"Skulker": "Проныра",
|
||||||
|
"Sleep": "Сон",
|
||||||
|
"Sleet storm": "Метель",
|
||||||
|
"Sleight of Hand": "Ловкость рук",
|
||||||
|
"Sling": "Праща",
|
||||||
|
"Slow": "Замедление",
|
||||||
|
"Slow Fall": "Замедление падения",
|
||||||
|
"Snilloc's Snowball Swarm": "Снежный шквал Сниллока",
|
||||||
|
"Soldier": "Солдат",
|
||||||
|
"Sorcerer": "чародей",
|
||||||
|
"Sorcerous Burst": "Чародейский выброс",
|
||||||
|
"Spare the Dying": "Уход за умирающим",
|
||||||
|
"Spare the dying": "Уход за умирающим",
|
||||||
|
"Speak with Animals": "Разговор с животными",
|
||||||
|
"Speak with Dead": "Разговор с мертвым",
|
||||||
|
"Speak with animals": "Разговор с животными",
|
||||||
|
"Speak with dead": "Разговор с мёртвыми",
|
||||||
|
"Speak with plants": "Разговор с растениями",
|
||||||
|
"Spear": "Копьё",
|
||||||
|
"Spell Save DC": "КС испытаний против заклинаний",
|
||||||
|
"Spell Slot": "ячейка заклинания",
|
||||||
|
"Spell Sniper": "Меткие заклинания",
|
||||||
|
"Spellcasting": "накладывание заклинаний",
|
||||||
|
"Spider climb": "Паук",
|
||||||
|
"Spike growth": "Шипы",
|
||||||
|
"Spirit Guardians": "Призрачные стражи",
|
||||||
|
"Spirit guardians": "Духовные стражи",
|
||||||
|
"Spiritual Weapon": "Призрачное оружие",
|
||||||
|
"Spiritual weapon": "Божественное оружие",
|
||||||
|
"Stable": "Стабилизированный",
|
||||||
|
"Staggering smite": "Оглушающая кара",
|
||||||
|
"Starry Wisp": "Звёздная искра",
|
||||||
|
"Stealth": "Скрытность",
|
||||||
|
"Steed Action Point": "Очко действия скакуна",
|
||||||
|
"Steed Attack": "Атака скакуна",
|
||||||
|
"Steel Wind Strike": "Удар стального ветра",
|
||||||
|
"Step of the Wind": "Поступь ветра",
|
||||||
|
"Stinking Cloud": "Смрадное облако",
|
||||||
|
"Stinking cloud": "Зловонное облако",
|
||||||
|
"Stone shape": "Изменение формы камня",
|
||||||
|
"Stoneskin": "Каменная кожа",
|
||||||
|
"Storm of vengeance": "Гроза гнева",
|
||||||
|
"Strength": "Сила",
|
||||||
|
"Stunned": "Ошеломлённый",
|
||||||
|
"Suggestion": "Внушение",
|
||||||
|
"Sunbeam": "Солнечный луч",
|
||||||
|
"Sunburst": "Солнечный ожог",
|
||||||
|
"Survival": "Выживание",
|
||||||
|
"Swift quiver": "Быстрый колчан",
|
||||||
|
"Sword Burst": "Вспышка мечей",
|
||||||
|
"Symbol": "Знак",
|
||||||
|
"Synaptic Static": "Синаптический разряд",
|
||||||
|
"Tactical Master": "Мастер тактики",
|
||||||
|
"Tactical Mind": "Тактический ум",
|
||||||
|
"Tactical Shift": "Тактический манёвр",
|
||||||
|
"Tasha's Hideous Laughter": "Отвратительный смех Таши",
|
||||||
|
"Tasha's Mind Whip": "Психическая плеть Таши",
|
||||||
|
"Tasha's hideous laughter": "Отвратительный смех Таши",
|
||||||
|
"Tavern Brawler": "Драчун",
|
||||||
|
"Telekinesis": "Телекинез",
|
||||||
|
"Telepathy": "Телепатия",
|
||||||
|
"Teleport": "Телепортация",
|
||||||
|
"Teleportation circle": "Круг телепортации",
|
||||||
|
"Temporary Hit Point": "временный ОЗ",
|
||||||
|
"Temporary Hit Points": "временные ОЗ",
|
||||||
|
"Tenser’s floating disk": "Тензеров парящий диск",
|
||||||
|
"Thaumaturge": "Чудотворец",
|
||||||
|
"Thaumaturgy": "Тавматургия",
|
||||||
|
"Thorn Whip": "Терновый хлыст",
|
||||||
|
"Thorn whip": "Терновый кнут",
|
||||||
|
"Throw": "Метнуть",
|
||||||
|
"Thunderclap": "Раскат грома",
|
||||||
|
"Thunderous smite": "Громовая кара",
|
||||||
|
"Thunderwave": "Громовая волна",
|
||||||
|
"Tiefling": "тифлинг",
|
||||||
|
"Time stop": "Остановка времени",
|
||||||
|
"Toll the Dead": "Погребальный звон",
|
||||||
|
"Tongues": "Языки",
|
||||||
|
"Topple": "Опрокидывание",
|
||||||
|
"Tough": "Крепкий",
|
||||||
|
"Transport via plants": "Путешествие через растения",
|
||||||
|
"Tree stride": "Древесный путь",
|
||||||
|
"Trickster’s Transposition": "Перемещение обманщика",
|
||||||
|
"Trident": "Трезубец",
|
||||||
|
"True Strike": "Верный удар",
|
||||||
|
"True polymorph": "Истинное превращение",
|
||||||
|
"True resurrection": "Истинное воскрешение",
|
||||||
|
"True seeing": "Истинное зрение",
|
||||||
|
"True strike": "Верный удар",
|
||||||
|
"Tsunami": "Цунами",
|
||||||
|
"Unarmored Defense": "Защита без доспехов",
|
||||||
|
"Unarmored Movement": "Движение без доспехов",
|
||||||
|
"Unarmoured Defence": "Защита без доспехов",
|
||||||
|
"Unarmoured Movement": "Движение без доспехов",
|
||||||
|
"Uncanny Metabolism": "Необычный метаболизм",
|
||||||
|
"Unconscious": "Бессознательный",
|
||||||
|
"Unfettered Mind": "Свободный разум",
|
||||||
|
"Unseen servant": "Невидимый слуга",
|
||||||
|
"Urchin": "Беспризорник",
|
||||||
|
"Vampiric Touch": "Касание вампира",
|
||||||
|
"Vampiric touch": "Прикосновение вампира",
|
||||||
|
"Vex": "Досада",
|
||||||
|
"Vicious Mockery": "Язвительная насмешка",
|
||||||
|
"Vicious mockery": "Злая насмешка",
|
||||||
|
"Vitriolic Sphere": "Едкий шар",
|
||||||
|
"Wall of fire": "Огненная стена",
|
||||||
|
"Wall of force": "Силовая стена",
|
||||||
|
"Wall of ice": "Ледяная стена",
|
||||||
|
"Wall of stone": "Каменная стена",
|
||||||
|
"Wall of thorns": "Терновая стена",
|
||||||
|
"War Caster": "Боевой заклинатель",
|
||||||
|
"War pick": "Боевая кирка",
|
||||||
|
"Warden": "Страж",
|
||||||
|
"Warding Flare": "Защитная вспышка",
|
||||||
|
"Warding bond": "Охраняющая связь",
|
||||||
|
"Warhammer": "Боевой молот",
|
||||||
|
"Warlock": "колдун",
|
||||||
|
"Water breathing": "Подводное дыхание",
|
||||||
|
"Water walk": "Хождение по воде",
|
||||||
|
"Weapon Master": "Мастер оружия",
|
||||||
|
"Weapon Mastery": "Мастерство владения оружием",
|
||||||
|
"Web": "Паутина",
|
||||||
|
"Weird": "Смертный ужас",
|
||||||
|
"Whip": "Кнут",
|
||||||
|
"Wild Resurgence": "Возрождение дикого облика",
|
||||||
|
"Wild Shape": "Дикий облик",
|
||||||
|
"Wild Shape Charge": "заряд «Дикого облика»",
|
||||||
|
"Wind walk": "Хождение по ветру",
|
||||||
|
"Wind wall": "Стена ветров",
|
||||||
|
"Wisdom": "Мудрость",
|
||||||
|
"Wish": "Исполнение желаний",
|
||||||
|
"Witch Bolt": "Ведьмовской разряд",
|
||||||
|
"Witch bolt": "Ведьмин снаряд",
|
||||||
|
"Wizard": "волшебник",
|
||||||
|
"Word of Radiance": "Слово сияния",
|
||||||
|
"Word of recall": "Слово возврата",
|
||||||
|
"Wrathful smite": "Гневная кара",
|
||||||
|
"Zone of truth": "Область истины"
|
||||||
|
}
|
||||||
210
scripts/apply-translation-edits.ps1
Normal file
210
scripts/apply-translation-edits.ps1
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
param(
|
||||||
|
[string]$RussianPath = "Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml",
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$EditsPath
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Read-XmlDocument {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
$resolvedPath = [System.IO.Path]::GetFullPath($Path)
|
||||||
|
if (-not (Test-Path -LiteralPath $resolvedPath)) {
|
||||||
|
throw "XML file was not found: '$resolvedPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
[xml]$xml = Get-Content -LiteralPath $resolvedPath -Raw
|
||||||
|
if ($null -eq $xml.SelectSingleNode('/contentList')) {
|
||||||
|
throw "XML file does not contain '/contentList': '$resolvedPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
return @{
|
||||||
|
Path = $resolvedPath
|
||||||
|
Xml = $xml
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-ContentNodeMap {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[xml]$Xml
|
||||||
|
)
|
||||||
|
|
||||||
|
$map = @{}
|
||||||
|
foreach ($node in $Xml.SelectNodes('/contentList/content')) {
|
||||||
|
$contentUid = [string]$node.GetAttribute("contentuid")
|
||||||
|
if ([string]::IsNullOrWhiteSpace($contentUid)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($map.ContainsKey($contentUid)) {
|
||||||
|
throw "Duplicate contentuid found in target XML: '$contentUid'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$map[$contentUid] = $node
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map
|
||||||
|
}
|
||||||
|
|
||||||
|
function Assert-UniqueEditContentUid {
|
||||||
|
param(
|
||||||
|
[AllowEmptyCollection()]
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[System.Collections.Generic.HashSet[string]]$Seen,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$ContentUid,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Section
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not $Seen.Add($ContentUid)) {
|
||||||
|
throw "Edits file contains duplicate contentuid '$ContentUid' in '$Section'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$russianDocument = Read-XmlDocument -Path $RussianPath
|
||||||
|
$temporaryRussianPath = "$($russianDocument.Path).tmp"
|
||||||
|
$validateScriptPath = Join-Path $PSScriptRoot "validate-translation-xml.ps1"
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $validateScriptPath)) {
|
||||||
|
throw "Validation script was not found: '$validateScriptPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $temporaryRussianPath) {
|
||||||
|
Remove-Item -LiteralPath $temporaryRussianPath -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
Copy-Item -LiteralPath $russianDocument.Path -Destination $temporaryRussianPath -Force
|
||||||
|
$russianDocument = Read-XmlDocument -Path $temporaryRussianPath
|
||||||
|
$resolvedEditsPath = [System.IO.Path]::GetFullPath($EditsPath)
|
||||||
|
if (-not (Test-Path -LiteralPath $resolvedEditsPath)) {
|
||||||
|
throw "Edits file was not found: '$resolvedEditsPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$edits = Get-Content -LiteralPath $resolvedEditsPath -Raw | ConvertFrom-Json -Depth 10
|
||||||
|
if ($null -eq $edits) {
|
||||||
|
throw "Edits file is empty or invalid JSON: '$resolvedEditsPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$contentListNode = $russianDocument.Xml.SelectSingleNode('/contentList')
|
||||||
|
if ($null -eq $contentListNode) {
|
||||||
|
throw "Target russian.xml does not contain '/contentList': '$($russianDocument.Path)'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$nodeMap = Get-ContentNodeMap -Xml $russianDocument.Xml
|
||||||
|
$updatedEntries = New-Object System.Collections.Generic.List[string]
|
||||||
|
$addedEntries = New-Object System.Collections.Generic.List[string]
|
||||||
|
$seenEditContentUids = [System.Collections.Generic.HashSet[string]]::new()
|
||||||
|
|
||||||
|
$updates = @()
|
||||||
|
if ($edits.PSObject.Properties.Name -contains "updates" -and $null -ne $edits.updates) {
|
||||||
|
$updates = @($edits.updates)
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($edit in $updates) {
|
||||||
|
$contentUid = [string]$edit.contentuid
|
||||||
|
if ([string]::IsNullOrWhiteSpace($contentUid)) {
|
||||||
|
throw "Each update entry must contain non-empty 'contentuid'."
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert-UniqueEditContentUid -Seen $seenEditContentUids -ContentUid $contentUid -Section "updates"
|
||||||
|
|
||||||
|
if (-not $nodeMap.ContainsKey($contentUid)) {
|
||||||
|
throw "Target russian.xml does not contain contentuid '$contentUid' for update."
|
||||||
|
}
|
||||||
|
|
||||||
|
$node = $nodeMap[$contentUid]
|
||||||
|
if ($edit.PSObject.Properties.Name -contains "version" -and -not [string]::IsNullOrWhiteSpace([string]$edit.version)) {
|
||||||
|
$node.SetAttribute("version", [string]$edit.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($edit.PSObject.Properties.Name -contains "text") {
|
||||||
|
if ([string]::IsNullOrWhiteSpace([string]$edit.text)) {
|
||||||
|
throw "Update entry '$contentUid' must contain non-empty 'text' when provided."
|
||||||
|
}
|
||||||
|
$node.InnerText = [string]$edit.text
|
||||||
|
}
|
||||||
|
|
||||||
|
$updatedEntries.Add($contentUid) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$adds = @()
|
||||||
|
if ($edits.PSObject.Properties.Name -contains "adds" -and $null -ne $edits.adds) {
|
||||||
|
$adds = @($edits.adds)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($updates.Count -eq 0) -and ($adds.Count -eq 0)) {
|
||||||
|
if (Test-Path -LiteralPath $temporaryRussianPath) {
|
||||||
|
Remove-Item -LiteralPath $temporaryRussianPath -Force
|
||||||
|
}
|
||||||
|
Write-Host "[apply-translation-edits.ps1] No edits requested. Original russian.xml left unchanged."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($edit in $adds) {
|
||||||
|
$contentUid = [string]$edit.contentuid
|
||||||
|
$version = [string]$edit.version
|
||||||
|
$text = [string]$edit.text
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($contentUid)) {
|
||||||
|
throw "Each add entry must contain non-empty 'contentuid'."
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert-UniqueEditContentUid -Seen $seenEditContentUids -ContentUid $contentUid -Section "adds"
|
||||||
|
|
||||||
|
if ($nodeMap.ContainsKey($contentUid)) {
|
||||||
|
throw "Target russian.xml already contains contentuid '$contentUid'; use 'updates' instead of 'adds'."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($version)) {
|
||||||
|
throw "Add entry '$contentUid' must contain non-empty 'version'."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($text)) {
|
||||||
|
throw "Add entry '$contentUid' must contain non-empty 'text'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$newNode = $russianDocument.Xml.CreateElement("content")
|
||||||
|
$newNode.SetAttribute("contentuid", $contentUid)
|
||||||
|
$newNode.SetAttribute("version", $version)
|
||||||
|
$newNode.InnerText = $text
|
||||||
|
[void]$contentListNode.AppendChild($newNode)
|
||||||
|
$nodeMap[$contentUid] = $newNode
|
||||||
|
$addedEntries.Add($contentUid) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = New-Object System.Xml.XmlWriterSettings
|
||||||
|
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
$settings.Indent = $true
|
||||||
|
$settings.IndentChars = " "
|
||||||
|
$settings.NewLineChars = "`n"
|
||||||
|
$settings.NewLineHandling = [System.Xml.NewLineHandling]::Replace
|
||||||
|
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($russianDocument.Path, $settings)
|
||||||
|
try {
|
||||||
|
$russianDocument.Xml.WriteTo($writer)
|
||||||
|
} finally {
|
||||||
|
$writer.Dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
& $validateScriptPath -XmlPath $temporaryRussianPath
|
||||||
|
Move-Item -LiteralPath $temporaryRussianPath -Destination $RussianPath -Force
|
||||||
|
} finally {
|
||||||
|
if (Test-Path -LiteralPath $temporaryRussianPath) {
|
||||||
|
Remove-Item -LiteralPath $temporaryRussianPath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[apply-translation-edits.ps1] Updated entries: $($updatedEntries.Count); Added entries: $($addedEntries.Count)."
|
||||||
|
if ($updatedEntries.Count -gt 0) {
|
||||||
|
Write-Host "[apply-translation-edits.ps1] Updated contentuid: $($updatedEntries -join ', ')"
|
||||||
|
}
|
||||||
|
if ($addedEntries.Count -gt 0) {
|
||||||
|
Write-Host "[apply-translation-edits.ps1] Added contentuid: $($addedEntries -join ', ')"
|
||||||
|
}
|
||||||
@@ -7,8 +7,8 @@ param(
|
|||||||
[string]$ArchiveBaseName = "DnD 5.5e AIO Russian",
|
[string]$ArchiveBaseName = "DnD 5.5e AIO Russian",
|
||||||
[string]$ModName = "DnD 5.5e All-in-One BEYOND Russian Localization",
|
[string]$ModName = "DnD 5.5e All-in-One BEYOND Russian Localization",
|
||||||
[string]$ModUuid = "6401e84d-daf2-416d-adeb-99c03a2487a6",
|
[string]$ModUuid = "6401e84d-daf2-416d-adeb-99c03a2487a6",
|
||||||
[string]$ModAuthor = "MikhailRaw",
|
[string]$ModAuthor = "Underslumber Team",
|
||||||
[string]$ModDescription = "Russian Localization",
|
[string]$ModDescription = "Русская локализация мода, который добавляет и обновляет контент в соответствии с правилами DnD 5.5e и другими источниками, включая предыстории, классы, таланты, расы, заклинания и многое другое. Это отдельный мод локализации и он требует установленный оригинальный мод.",
|
||||||
[string]$ModVersion64 = "36028797018963968",
|
[string]$ModVersion64 = "36028797018963968",
|
||||||
[string]$ModGroup = "6401e84d-daf2-416d-adeb-99c03a2487a6",
|
[string]$ModGroup = "6401e84d-daf2-416d-adeb-99c03a2487a6",
|
||||||
[string]$DependencyUuid = "897914ef-5c96-053c-44af-0be823f895fe",
|
[string]$DependencyUuid = "897914ef-5c96-053c-44af-0be823f895fe",
|
||||||
@@ -101,9 +101,10 @@ if (-not (Test-Path -LiteralPath $stagedMetaPath)) {
|
|||||||
throw "Staged meta.lsx was not found: '$stagedMetaPath'."
|
throw "Staged meta.lsx was not found: '$stagedMetaPath'."
|
||||||
}
|
}
|
||||||
|
|
||||||
$stagedMetaContent = Get-Content -LiteralPath $stagedMetaPath -Raw
|
$utf8Encoding = [System.Text.UTF8Encoding]::new($false)
|
||||||
|
$stagedMetaContent = [System.IO.File]::ReadAllText($stagedMetaPath, $utf8Encoding)
|
||||||
$stagedMetaContent = $stagedMetaContent -replace '(<attribute id="Version64" type="int64" value=")\d+("/>)', "`${1}$resolvedVersion64`${2}"
|
$stagedMetaContent = $stagedMetaContent -replace '(<attribute id="Version64" type="int64" value=")\d+("/>)', "`${1}$resolvedVersion64`${2}"
|
||||||
Set-Content -LiteralPath $stagedMetaPath -Value $stagedMetaContent -Encoding utf8
|
[System.IO.File]::WriteAllText($stagedMetaPath, $stagedMetaContent, $utf8Encoding)
|
||||||
|
|
||||||
Write-Host "[build.ps1] Staged source tree:"
|
Write-Host "[build.ps1] Staged source tree:"
|
||||||
Get-ChildItem -Recurse $stagingPath | Select-Object FullName, Length | Format-Table -AutoSize
|
Get-ChildItem -Recurse $stagingPath | Select-Object FullName, Length | Format-Table -AutoSize
|
||||||
@@ -112,6 +113,8 @@ if (Test-Path -LiteralPath $tempPackagePath) {
|
|||||||
Remove-Item -LiteralPath $tempPackagePath -Force
|
Remove-Item -LiteralPath $tempPackagePath -Force
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# CI quirk: Divine can occasionally emit a broken ~48-byte package for some source roots.
|
||||||
|
# Mitigation: try staged/mods/workspace sources and accept only outputs that look valid by size.
|
||||||
$packageAttempts = @(
|
$packageAttempts = @(
|
||||||
[ordered]@{ Name = "staging-root"; Source = $stagingPath },
|
[ordered]@{ Name = "staging-root"; Source = $stagingPath },
|
||||||
[ordered]@{ Name = "mods-root"; Source = $modsPath },
|
[ordered]@{ Name = "mods-root"; Source = $modsPath },
|
||||||
|
|||||||
209
scripts/compare-translation.ps1
Normal file
209
scripts/compare-translation.ps1
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
param(
|
||||||
|
[string]$EnglishPath = ".cache/upstream/english.xml",
|
||||||
|
[string]$RussianPath = "Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml",
|
||||||
|
[string]$OutputDir = "build/translation-diff"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Get-LocalizationEntries {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
$resolvedPath = [System.IO.Path]::GetFullPath($Path)
|
||||||
|
if (-not (Test-Path -LiteralPath $resolvedPath)) {
|
||||||
|
throw "Localization XML was not found: '$resolvedPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
[xml]$xml = Get-Content -LiteralPath $resolvedPath -Raw
|
||||||
|
$nodes = $xml.SelectNodes('/contentList/content')
|
||||||
|
if ($null -eq $nodes) {
|
||||||
|
throw "Localization XML does not contain '/contentList/content' nodes: '$resolvedPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries = @{}
|
||||||
|
foreach ($node in $nodes) {
|
||||||
|
$contentUid = [string]$node.GetAttribute("contentuid")
|
||||||
|
if ([string]::IsNullOrWhiteSpace($contentUid)) {
|
||||||
|
throw "Localization XML contains a content node without 'contentuid': '$resolvedPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($entries.ContainsKey($contentUid)) {
|
||||||
|
throw "Localization XML contains duplicate contentuid '$contentUid': '$resolvedPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = [string]$node.GetAttribute("version")
|
||||||
|
if ([string]::IsNullOrWhiteSpace($version)) {
|
||||||
|
throw "Localization XML contains contentuid '$contentUid' with empty 'version': '$resolvedPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries[$contentUid] = [ordered]@{
|
||||||
|
contentuid = $contentUid
|
||||||
|
version = $version
|
||||||
|
text = [string]$node.InnerText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entries
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolvedOutputDir = [System.IO.Path]::GetFullPath($OutputDir)
|
||||||
|
New-Item -ItemType Directory -Path $resolvedOutputDir -Force | Out-Null
|
||||||
|
|
||||||
|
$englishEntries = Get-LocalizationEntries -Path $EnglishPath
|
||||||
|
$russianEntries = Get-LocalizationEntries -Path $RussianPath
|
||||||
|
|
||||||
|
$missingInRussian = New-Object System.Collections.Generic.List[object]
|
||||||
|
$versionMismatch = New-Object System.Collections.Generic.List[object]
|
||||||
|
$staleOnlyInRussian = New-Object System.Collections.Generic.List[object]
|
||||||
|
|
||||||
|
foreach ($contentUid in ($englishEntries.Keys | Sort-Object)) {
|
||||||
|
$englishEntry = $englishEntries[$contentUid]
|
||||||
|
if (-not $russianEntries.ContainsKey($contentUid)) {
|
||||||
|
$missingInRussian.Add([pscustomobject]@{
|
||||||
|
contentuid = $contentUid
|
||||||
|
englishVersion = $englishEntry.version
|
||||||
|
englishText = $englishEntry.text
|
||||||
|
}) | Out-Null
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$russianEntry = $russianEntries[$contentUid]
|
||||||
|
if ($englishEntry.version -ne $russianEntry.version) {
|
||||||
|
$versionMismatch.Add([pscustomobject]@{
|
||||||
|
contentuid = $contentUid
|
||||||
|
englishVersion = $englishEntry.version
|
||||||
|
russianVersion = $russianEntry.version
|
||||||
|
englishText = $englishEntry.text
|
||||||
|
russianText = $russianEntry.text
|
||||||
|
}) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($contentUid in ($russianEntries.Keys | Sort-Object)) {
|
||||||
|
if (-not $englishEntries.ContainsKey($contentUid)) {
|
||||||
|
$russianEntry = $russianEntries[$contentUid]
|
||||||
|
$staleOnlyInRussian.Add([pscustomobject]@{
|
||||||
|
contentuid = $contentUid
|
||||||
|
russianVersion = $russianEntry.version
|
||||||
|
russianText = $russianEntry.text
|
||||||
|
}) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$summary = [ordered]@{
|
||||||
|
generatedAt = (Get-Date).ToString("o")
|
||||||
|
englishPath = [System.IO.Path]::GetFullPath($EnglishPath)
|
||||||
|
russianPath = [System.IO.Path]::GetFullPath($RussianPath)
|
||||||
|
englishCount = $englishEntries.Count
|
||||||
|
russianCount = $russianEntries.Count
|
||||||
|
missingInRussianCount = $missingInRussian.Count
|
||||||
|
versionMismatchCount = $versionMismatch.Count
|
||||||
|
staleOnlyInRussianCount = $staleOnlyInRussian.Count
|
||||||
|
missingInRussian = $missingInRussian
|
||||||
|
versionMismatch = $versionMismatch
|
||||||
|
staleOnlyInRussian = $staleOnlyInRussian
|
||||||
|
}
|
||||||
|
|
||||||
|
$summaryJsonPath = Join-Path $resolvedOutputDir "summary.json"
|
||||||
|
$summaryMdPath = Join-Path $resolvedOutputDir "summary.md"
|
||||||
|
$candidatesJsonPath = Join-Path $resolvedOutputDir "candidates.json"
|
||||||
|
|
||||||
|
$summary | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $summaryJsonPath -Encoding utf8
|
||||||
|
|
||||||
|
$candidates = [ordered]@{
|
||||||
|
generatedAt = (Get-Date).ToString("o")
|
||||||
|
source = [ordered]@{
|
||||||
|
englishPath = [System.IO.Path]::GetFullPath($EnglishPath)
|
||||||
|
russianPath = [System.IO.Path]::GetFullPath($RussianPath)
|
||||||
|
}
|
||||||
|
updates = @(
|
||||||
|
$versionMismatch | ForEach-Object {
|
||||||
|
[ordered]@{
|
||||||
|
contentuid = $_.contentuid
|
||||||
|
version = $_.englishVersion
|
||||||
|
text = $_.russianText
|
||||||
|
englishText = $_.englishText
|
||||||
|
russianVersion = $_.russianVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
adds = @(
|
||||||
|
$missingInRussian | ForEach-Object {
|
||||||
|
[ordered]@{
|
||||||
|
contentuid = $_.contentuid
|
||||||
|
version = $_.englishVersion
|
||||||
|
text = ""
|
||||||
|
englishText = $_.englishText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
$candidates | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $candidatesJsonPath -Encoding utf8
|
||||||
|
|
||||||
|
$isUpToDate = ($missingInRussian.Count -eq 0) -and ($versionMismatch.Count -eq 0) -and ($staleOnlyInRussian.Count -eq 0)
|
||||||
|
|
||||||
|
$mdLines = @(
|
||||||
|
"# Translation diff summary",
|
||||||
|
"",
|
||||||
|
"- Generated: $($summary.generatedAt)",
|
||||||
|
"- English entries: $($summary.englishCount)",
|
||||||
|
"- Russian entries: $($summary.russianCount)",
|
||||||
|
"- Missing in Russian: $($summary.missingInRussianCount)",
|
||||||
|
"- Version mismatches: $($summary.versionMismatchCount)",
|
||||||
|
"- Stale only in Russian: $($summary.staleOnlyInRussianCount)",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($isUpToDate) {
|
||||||
|
$mdLines += "Перевод уже актуален, дополнительные действия не требуются."
|
||||||
|
} else {
|
||||||
|
$mdLines += ""
|
||||||
|
$mdLines += "## Agent workflow"
|
||||||
|
$mdLines += "1. Refresh upstream cache: ``scripts/get-upstream-english.ps1``"
|
||||||
|
$mdLines += "2. Refresh diff reports: ``scripts/compare-translation.ps1``"
|
||||||
|
$mdLines += "3. Fill translated texts in ``build/translation-diff/candidates.json``"
|
||||||
|
$mdLines += "4. Apply only prepared edits: ``scripts/apply-translation-edits.ps1 -EditsPath build/translation-diff/candidates.json``"
|
||||||
|
}
|
||||||
|
|
||||||
|
$mdLines += ""
|
||||||
|
$mdLines += "## Missing in Russian"
|
||||||
|
if ($missingInRussian.Count -eq 0) {
|
||||||
|
$mdLines += "- none"
|
||||||
|
} else {
|
||||||
|
$mdLines += ($missingInRussian | Select-Object -First 50 | ForEach-Object {
|
||||||
|
"- ``$($_.contentuid)`` v$($_.englishVersion): $($_.englishText)"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$mdLines += ""
|
||||||
|
$mdLines += "## Version mismatches"
|
||||||
|
if ($versionMismatch.Count -eq 0) {
|
||||||
|
$mdLines += "- none"
|
||||||
|
} else {
|
||||||
|
$mdLines += ($versionMismatch | Select-Object -First 50 | ForEach-Object {
|
||||||
|
"- ``$($_.contentuid)`` en=v$($_.englishVersion), ru=v$($_.russianVersion)"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$mdLines += ""
|
||||||
|
$mdLines += "## Stale only in Russian"
|
||||||
|
if ($staleOnlyInRussian.Count -eq 0) {
|
||||||
|
$mdLines += "- none"
|
||||||
|
} else {
|
||||||
|
$mdLines += ($staleOnlyInRussian | Select-Object -First 50 | ForEach-Object {
|
||||||
|
"- ``$($_.contentuid)`` v$($_.russianVersion): $($_.russianText)"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Content -LiteralPath $summaryMdPath -Value $mdLines -Encoding utf8
|
||||||
|
|
||||||
|
Write-Host "[compare-translation.ps1] Summary written to '$summaryJsonPath' and '$summaryMdPath'."
|
||||||
|
Write-Host "[compare-translation.ps1] Editable candidate file written to '$candidatesJsonPath'."
|
||||||
|
Write-Host "[compare-translation.ps1] Missing=$($missingInRussian.Count); VersionMismatch=$($versionMismatch.Count); StaleOnlyInRussian=$($staleOnlyInRussian.Count)."
|
||||||
|
if ($isUpToDate) {
|
||||||
|
Write-Host "[compare-translation.ps1] Перевод уже актуален, дополнительные действия не требуются."
|
||||||
|
}
|
||||||
44
scripts/get-upstream-english.ps1
Normal file
44
scripts/get-upstream-english.ps1
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
param(
|
||||||
|
[string]$UpstreamEnglishUrl = "https://raw.githubusercontent.com/Yoonmoonsik/dnd55e/main/Mods/DnD2024_897914ef-5c96-053c-44af-0be823f895fe/Localization/English/english.xml",
|
||||||
|
[string]$OutputPath = ".cache/upstream/english.xml",
|
||||||
|
[switch]$Force
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($UpstreamEnglishUrl)) {
|
||||||
|
throw "UpstreamEnglishUrl must not be empty."
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolvedOutputPath = [System.IO.Path]::GetFullPath($OutputPath)
|
||||||
|
$outputDirectory = Split-Path -Parent $resolvedOutputPath
|
||||||
|
if (-not (Test-Path -LiteralPath $outputDirectory)) {
|
||||||
|
New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$requestParameters = @{
|
||||||
|
Uri = $UpstreamEnglishUrl
|
||||||
|
OutFile = $resolvedOutputPath
|
||||||
|
UseBasicParsing = $true
|
||||||
|
TimeoutSec = 120
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Force) {
|
||||||
|
$requestParameters["Headers"] = @{
|
||||||
|
"Cache-Control" = "no-cache"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-WebRequest @requestParameters
|
||||||
|
|
||||||
|
[xml]$englishXml = Get-Content -LiteralPath $resolvedOutputPath -Raw
|
||||||
|
$rootNode = $englishXml.SelectSingleNode('/contentList')
|
||||||
|
if ($null -eq $rootNode) {
|
||||||
|
throw "Downloaded file is not a valid BG3 localization XML: '$resolvedOutputPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$contentCount = $englishXml.SelectNodes('/contentList/content').Count
|
||||||
|
$fileInfo = Get-Item -LiteralPath $resolvedOutputPath
|
||||||
|
|
||||||
|
Write-Host "[get-upstream-english.ps1] Saved upstream english.xml to '$resolvedOutputPath'."
|
||||||
|
Write-Host "[get-upstream-english.ps1] Entries: $contentCount; Size: $($fileInfo.Length) bytes; Updated: $($fileInfo.LastWriteTime.ToString("o"))."
|
||||||
63
scripts/set-version.ps1
Normal file
63
scripts/set-version.ps1
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$VersionTag,
|
||||||
|
[string]$MetaPath = "Mods/DnD 5.5e AIO Russian/meta.lsx"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Convert-VersionTagToVersion64 {
|
||||||
|
param(
|
||||||
|
[string]$Tag
|
||||||
|
)
|
||||||
|
|
||||||
|
$normalized = $Tag
|
||||||
|
if ($normalized.StartsWith("v")) {
|
||||||
|
$normalized = $normalized.Substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($normalized -notmatch '^\d+(\.\d+){0,3}$') {
|
||||||
|
throw "Version tag '$Tag' is invalid. Expected format: vX.Y.Z or X.Y.Z"
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = $normalized.Split(".")
|
||||||
|
$numbers = @(0, 0, 0, 0)
|
||||||
|
for ($i = 0; $i -lt $parts.Length; $i++) {
|
||||||
|
$numbers[$i] = [int]$parts[$i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ([int64]$numbers[0] -shl 55) -bor ([int64]$numbers[1] -shl 47) -bor ([int64]$numbers[2] -shl 31) -bor [int64]$numbers[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolvedMetaPath = [System.IO.Path]::GetFullPath($MetaPath)
|
||||||
|
if (-not (Test-Path -LiteralPath $resolvedMetaPath)) {
|
||||||
|
throw "meta.lsx was not found: '$resolvedMetaPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolvedVersion64 = Convert-VersionTagToVersion64 -Tag $VersionTag
|
||||||
|
$utf8Encoding = [System.Text.UTF8Encoding]::new($false)
|
||||||
|
$metaContent = [System.IO.File]::ReadAllText($resolvedMetaPath, $utf8Encoding)
|
||||||
|
[xml]$metaXml = $metaContent
|
||||||
|
|
||||||
|
# Explicitly target ModuleInfo/Version64 via XML path to avoid touching Dependencies/PublishVersion.
|
||||||
|
$moduleInfoVersionNode = $metaXml.SelectSingleNode('/save/region/node/children/node[@id="ModuleInfo"]/attribute[@id="Version64" and @type="int64"]')
|
||||||
|
if ($null -eq $moduleInfoVersionNode) {
|
||||||
|
throw "ModuleInfo/Version64 attribute was not found in '$resolvedMetaPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Replace only the Version64 attribute that appears inside ModuleInfo before its <children> block.
|
||||||
|
$moduleInfoVersionPattern = '(?s)(<node id="ModuleInfo">\s*(?:(?!<children>).)*?<attribute id="Version64" type="int64" value=")\d+("/>)'
|
||||||
|
if ($metaContent -notmatch $moduleInfoVersionPattern) {
|
||||||
|
throw "ModuleInfo/Version64 attribute was not found in '$resolvedMetaPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$updatedMeta = [regex]::Replace(
|
||||||
|
$metaContent,
|
||||||
|
$moduleInfoVersionPattern,
|
||||||
|
"`${1}$resolvedVersion64`${2}",
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
[System.IO.File]::WriteAllText($resolvedMetaPath, $updatedMeta, $utf8Encoding)
|
||||||
|
|
||||||
|
Write-Host "[set-version.ps1] Updated '$resolvedMetaPath' to Version64=$resolvedVersion64 (from tag '$VersionTag')."
|
||||||
91
scripts/sync-parent-meta.ps1
Normal file
91
scripts/sync-parent-meta.ps1
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
param(
|
||||||
|
[string]$ParentMetaUrl = "https://raw.githubusercontent.com/Yoonmoonsik/dnd55e/main/Mods/DnD2024_897914ef-5c96-053c-44af-0be823f895fe/meta.lsx",
|
||||||
|
[string]$TargetMetaPath = "Mods/DnD 5.5e AIO Russian/meta.lsx"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$resolvedTargetMetaPath = [System.IO.Path]::GetFullPath($TargetMetaPath)
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $resolvedTargetMetaPath)) {
|
||||||
|
throw "Target meta.lsx was not found: '$resolvedTargetMetaPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($ParentMetaUrl)) {
|
||||||
|
throw "ParentMetaUrl must not be empty."
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$parentResponse = Invoke-WebRequest -Uri $ParentMetaUrl -UseBasicParsing -TimeoutSec 60
|
||||||
|
} catch {
|
||||||
|
throw "Failed to download parent meta.lsx from '$ParentMetaUrl': $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$parentRaw = $parentResponse.Content
|
||||||
|
$targetRaw = Get-Content -LiteralPath $resolvedTargetMetaPath -Raw
|
||||||
|
|
||||||
|
[xml]$parentXml = $parentRaw
|
||||||
|
[xml]$targetXml = $targetRaw
|
||||||
|
|
||||||
|
$parentModuleInfo = $parentXml.SelectSingleNode('/save/region/node/children/node[@id="ModuleInfo"]')
|
||||||
|
if ($null -eq $parentModuleInfo) {
|
||||||
|
throw "ModuleInfo node was not found in parent meta downloaded from '$ParentMetaUrl'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$requiredFields = @("Folder", "MD5", "Name", "PublishHandle", "UUID", "Version64")
|
||||||
|
$sourceValues = @{}
|
||||||
|
|
||||||
|
foreach ($field in $requiredFields) {
|
||||||
|
$node = $parentModuleInfo.SelectSingleNode("attribute[@id='$field']")
|
||||||
|
if ($null -eq $node) {
|
||||||
|
throw "Required parent ModuleInfo attribute '$field' is missing in meta downloaded from '$ParentMetaUrl'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $node.GetAttribute("value")
|
||||||
|
if ([string]::IsNullOrWhiteSpace($value)) {
|
||||||
|
throw "Required parent ModuleInfo attribute '$field' has empty value in meta downloaded from '$ParentMetaUrl'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$sourceValues[$field] = $value
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetDependencyNode = $targetXml.SelectSingleNode('/save/region/node/children/node[@id="Dependencies"]/children/node[@id="ModuleShortDesc"]')
|
||||||
|
if ($null -eq $targetDependencyNode) {
|
||||||
|
throw "Dependencies/ModuleShortDesc node was not found in target meta: '$resolvedTargetMetaPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$changedFields = @()
|
||||||
|
foreach ($field in $requiredFields) {
|
||||||
|
$targetAttr = $targetDependencyNode.SelectSingleNode("attribute[@id='$field']")
|
||||||
|
if ($null -eq $targetAttr) {
|
||||||
|
throw "Target Dependencies/ModuleShortDesc attribute '$field' is missing in '$resolvedTargetMetaPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentValue = $targetAttr.GetAttribute("value")
|
||||||
|
$newValue = [string]$sourceValues[$field]
|
||||||
|
if ($currentValue -ne $newValue) {
|
||||||
|
$targetAttr.SetAttribute("value", $newValue)
|
||||||
|
$changedFields += $field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($changedFields.Count -eq 0) {
|
||||||
|
Write-Host "[sync-parent-meta.ps1] No changes needed. Target dependency data is already up to date."
|
||||||
|
} else {
|
||||||
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
$settings = New-Object System.Xml.XmlWriterSettings
|
||||||
|
$settings.Encoding = $utf8Bom
|
||||||
|
$settings.Indent = $true
|
||||||
|
$settings.IndentChars = " "
|
||||||
|
$settings.NewLineChars = "`n"
|
||||||
|
$settings.NewLineHandling = [System.Xml.NewLineHandling]::Replace
|
||||||
|
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($resolvedTargetMetaPath, $settings)
|
||||||
|
try {
|
||||||
|
$targetXml.WriteTo($writer)
|
||||||
|
} finally {
|
||||||
|
$writer.Dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ("[sync-parent-meta.ps1] Updated fields: " + ($changedFields -join ", "))
|
||||||
|
}
|
||||||
71
scripts/update-translation.ps1
Normal file
71
scripts/update-translation.ps1
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
param(
|
||||||
|
[string]$RussianPath = "Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml",
|
||||||
|
[string]$EnglishPath = ".cache/upstream/english.xml",
|
||||||
|
[string]$OutputDir = "build/translation-diff",
|
||||||
|
[string]$EditsPath = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$getUpstreamScriptPath = Join-Path $PSScriptRoot "get-upstream-english.ps1"
|
||||||
|
$compareScriptPath = Join-Path $PSScriptRoot "compare-translation.ps1"
|
||||||
|
$applyScriptPath = Join-Path $PSScriptRoot "apply-translation-edits.ps1"
|
||||||
|
|
||||||
|
foreach ($scriptPath in @($getUpstreamScriptPath, $compareScriptPath, $applyScriptPath)) {
|
||||||
|
if (-not (Test-Path -LiteralPath $scriptPath)) {
|
||||||
|
throw "Required script was not found: '$scriptPath'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolvedOutputDir = [System.IO.Path]::GetFullPath($OutputDir)
|
||||||
|
$resolvedProvidedEditsPath = ""
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($EditsPath)) {
|
||||||
|
$resolvedProvidedEditsPath = [System.IO.Path]::GetFullPath($EditsPath)
|
||||||
|
}
|
||||||
|
$workingDiffDir = Join-Path $env:TEMP ("bg3-translation-update-" + [guid]::NewGuid().ToString("N"))
|
||||||
|
New-Item -ItemType Directory -Path $workingDiffDir -Force | Out-Null
|
||||||
|
|
||||||
|
try {
|
||||||
|
& $getUpstreamScriptPath -OutputPath $EnglishPath -Force
|
||||||
|
& $compareScriptPath -EnglishPath $EnglishPath -RussianPath $RussianPath -OutputDir $workingDiffDir
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Path $resolvedOutputDir -Force | Out-Null
|
||||||
|
Get-ChildItem -LiteralPath $workingDiffDir | ForEach-Object {
|
||||||
|
$destinationPath = Join-Path $resolvedOutputDir $_.Name
|
||||||
|
if ($resolvedProvidedEditsPath -and ([System.IO.Path]::GetFullPath($destinationPath) -eq $resolvedProvidedEditsPath) -and (Test-Path -LiteralPath $resolvedProvidedEditsPath)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Copy-Item -LiteralPath $_.FullName -Destination $resolvedOutputDir -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
$summaryJsonPath = Join-Path $workingDiffDir "summary.json"
|
||||||
|
if (-not (Test-Path -LiteralPath $summaryJsonPath)) {
|
||||||
|
throw "Translation summary was not found: '$summaryJsonPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$summary = Get-Content -LiteralPath $summaryJsonPath -Raw | ConvertFrom-Json -Depth 10
|
||||||
|
$hasDiff = ($summary.missingInRussianCount -gt 0) -or ($summary.versionMismatchCount -gt 0) -or ($summary.staleOnlyInRussianCount -gt 0)
|
||||||
|
|
||||||
|
if (-not $hasDiff) {
|
||||||
|
Write-Host "[update-translation.ps1] Перевод уже актуален, дополнительные действия не требуются."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($EditsPath)) {
|
||||||
|
Write-Host "[update-translation.ps1] Найдены изменения перевода. Подготовьте правки в '$([System.IO.Path]::GetFullPath((Join-Path $resolvedOutputDir "candidates.json")))' и затем запустите повторно с '-EditsPath'."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$effectiveEditsPath = [System.IO.Path]::GetFullPath($EditsPath)
|
||||||
|
if (-not (Test-Path -LiteralPath $effectiveEditsPath)) {
|
||||||
|
throw "Prepared edits file was not found: '$effectiveEditsPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
& $applyScriptPath -RussianPath $RussianPath -EditsPath $effectiveEditsPath
|
||||||
|
|
||||||
|
Write-Host "[update-translation.ps1] Обновление перевода завершено. Результат записан в '$([System.IO.Path]::GetFullPath($RussianPath))'."
|
||||||
|
} finally {
|
||||||
|
if (Test-Path -LiteralPath $workingDiffDir) {
|
||||||
|
Remove-Item -LiteralPath $workingDiffDir -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
41
scripts/validate-translation-xml.ps1
Normal file
41
scripts/validate-translation-xml.ps1
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$XmlPath
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$resolvedXmlPath = [System.IO.Path]::GetFullPath($XmlPath)
|
||||||
|
if (-not (Test-Path -LiteralPath $resolvedXmlPath)) {
|
||||||
|
throw "XML file was not found: '$resolvedXmlPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
[xml]$xml = Get-Content -LiteralPath $resolvedXmlPath -Raw
|
||||||
|
$contentListNode = $xml.SelectSingleNode('/contentList')
|
||||||
|
if ($null -eq $contentListNode) {
|
||||||
|
throw "XML validation failed: missing '/contentList' in '$resolvedXmlPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$contentNodes = $xml.SelectNodes('/contentList/content')
|
||||||
|
if ($null -eq $contentNodes -or $contentNodes.Count -lt 1) {
|
||||||
|
throw "XML validation failed: no '/contentList/content' entries found in '$resolvedXmlPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$seen = [System.Collections.Generic.HashSet[string]]::new()
|
||||||
|
foreach ($node in $contentNodes) {
|
||||||
|
$contentUid = [string]$node.GetAttribute("contentuid")
|
||||||
|
if ([string]::IsNullOrWhiteSpace($contentUid)) {
|
||||||
|
throw "XML validation failed: found content node without 'contentuid' in '$resolvedXmlPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $seen.Add($contentUid)) {
|
||||||
|
throw "XML validation failed: duplicate contentuid '$contentUid' in '$resolvedXmlPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = [string]$node.GetAttribute("version")
|
||||||
|
if ([string]::IsNullOrWhiteSpace($version)) {
|
||||||
|
throw "XML validation failed: contentuid '$contentUid' has empty 'version' in '$resolvedXmlPath'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[validate-translation-xml.ps1] XML is valid: '$resolvedXmlPath'. Entries=$($contentNodes.Count)."
|
||||||
Reference in New Issue
Block a user