rpfm_lib/integrations/assembly_kit/localisable_fields.rs
1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! Localisable fields parsing for Assembly Kit integration.
12//!
13//! This module handles the parsing of Assembly Kit's localisable fields definition file,
14//! which identifies which table fields contain translatable text that should be extracted
15//! to `.loc` (localisation) files.
16//!
17//! # Overview
18//!
19//! Total War games support multiple languages through localisation files (`.loc` files).
20//! Rather than storing translated text directly in database tables, certain text fields
21//! are marked as "localisable" and their content is stored in separate translation files.
22//!
23//! The Assembly Kit includes a `TExc_LocalisableFields.xml` file that defines which
24//! fields in which tables should be treated as localisable.
25//!
26//! # File Format
27//!
28//! The localisable fields file is an XML file with this structure:
29//! ```xml
30//! <dataroot>
31//! <TExc_LocalisableFields>
32//! <table_name>units_tables</table_name>
33//! <field>onscreen_name</field>
34//! </TExc_LocalisableFields>
35//! <TExc_LocalisableFields>
36//! <table_name>units_tables</table_name>
37//! <field>short_description</field>
38//! </TExc_LocalisableFields>
39//! </dataroot>
40//! ```
41//!
42//! # Main Types
43//!
44//! - [`RawLocalisableFields`]: Root structure containing all localisable field definitions
45//! - [`RawLocalisableField`]: Single field marked as localisable
46//!
47//! # Availability
48//!
49//! Localisable fields files are only available in Assembly Kit versions 1 and 2:
50//! - **Version 0** (Empire/Napoleon): Not available - must be determined through analysis
51//! - **Version 1** (Shogun 2): Available as `TExc_LocalisableFields.xml`
52//! - **Version 2** (Rome 2+): Available as `TExc_LocalisableFields.xml`
53
54use serde_derive::Deserialize;
55use serde_xml_rs::from_reader;
56
57use std::fs::File;
58use std::io::BufReader;
59use std::path::Path;
60
61use crate::error::{Result, RLibError};
62
63use super::*;
64
65//---------------------------------------------------------------------------//
66// Types for parsing the Assembly Kit's TExc_LocalisableFields Files into.
67//---------------------------------------------------------------------------//
68
69/// Complete localisable fields definition from Assembly Kit.
70///
71/// This is the root structure parsed from `TExc_LocalisableFields.xml`. It contains
72/// a list of all table fields that should be treated as localisable.
73///
74/// # Structure
75///
76/// Each entry in the file maps a table field to its localisable status. Multiple
77/// fields from the same table will appear as separate entries.
78///
79/// # Example
80///
81/// For a units table with two localisable fields, the file would contain:
82/// ```xml
83/// <dataroot>
84/// <TExc_LocalisableFields>
85/// <table_name>units_tables</table_name>
86/// <field>onscreen_name</field>
87/// </TExc_LocalisableFields>
88/// <TExc_LocalisableFields>
89/// <table_name>units_tables</table_name>
90/// <field>description</field>
91/// </TExc_LocalisableFields>
92/// </dataroot>
93/// ```
94#[derive(Clone, Debug, Deserialize)]
95#[serde(rename = "dataroot")]
96pub struct RawLocalisableFields {
97
98 /// All localisable field definitions.
99 #[serde(rename = "TExc_LocalisableFields")]
100 pub fields: Vec<RawLocalisableField>,
101}
102
103/// Single localisable field definition.
104///
105/// Identifies one field in one table that contains translatable text.
106///
107/// # Fields
108///
109/// * `table_name` - Name of the table (without `_tables` suffix in some versions)
110/// * `field` - Name of the field/column that is localisable
111#[derive(Clone, Debug, Deserialize)]
112#[serde(rename = "datafield")]
113pub struct RawLocalisableField {
114 /// Table name containing the localisable field.
115 pub table_name: String,
116
117 /// Field/column name that is localisable.
118 pub field: String,
119}
120
121//---------------------------------------------------------------------------//
122// Implementations
123//---------------------------------------------------------------------------//
124
125/// Implementation of `RawLocalisableFields`.
126impl RawLocalisableFields {
127
128 /// Parses the localisable fields definition file from Assembly Kit.
129 ///
130 /// Reads and parses the `TExc_LocalisableFields.xml` file which lists all table
131 /// fields that contain translatable text.
132 ///
133 /// # Arguments
134 ///
135 /// * `raw_data_path` - Path to the Assembly Kit data directory
136 /// * `version` - Assembly Kit version (1 = Shogun 2, 2 = Rome 2+)
137 ///
138 /// # Returns
139 ///
140 /// Returns a [`RawLocalisableFields`] containing all field definitions.
141 ///
142 /// # Errors
143 ///
144 /// Returns an error if:
145 /// - The version is not 1 or 2 (returns [`RLibError::AssemblyKitUnsupportedVersion`])
146 /// - The `TExc_LocalisableFields.xml` file cannot be found or opened
147 /// - The XML is malformed
148 ///
149 /// # Version 0 Note
150 ///
151 /// Empire and Napoleon (Version 0) do not include a localisable fields file.
152 /// For these games, localisable fields must be determined through other means
153 /// (typically by analyzing actual game data or manual specification).
154 pub fn read(raw_data_path: &Path, version: i16) -> Result<Self> {
155 match version {
156 2 | 1 => {
157 let localisable_fields_path = get_raw_localisable_fields_path(raw_data_path, version)?;
158 let localisable_fields_file = BufReader::new(File::open(localisable_fields_path)?);
159 from_reader(localisable_fields_file).map_err(From::from)
160 }
161
162 // Version 0 doesn't have loc fields as is. We have to bruteforce them.
163 _ => Err(RLibError::AssemblyKitUnsupportedVersion(version))
164 }
165 }
166}