Skip to main content

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}