From edb4d2ae9788c400f9a34f2e50e4a83872b791a1 Mon Sep 17 00:00:00 2001 From: Brett Mayson Date: Sun, 20 Oct 2024 06:03:35 +0000 Subject: [PATCH] localization --- Cargo.lock | 22 + Cargo.toml | 1 + bin/Cargo.toml | 3 +- bin/src/commands/localization/coverage.rs | 152 ++ bin/src/commands/localization/mod.rs | 33 + bin/src/commands/localization/sort.rs | 44 + bin/src/commands/mod.rs | 1 + bin/src/commands/utils.rs | 2 + bin/src/lib.rs | 11 +- bin/src/utils/config/inspect.rs | 79 + bin/src/utils/config/mod.rs | 40 + bin/src/utils/inspect.rs | 146 +- bin/src/utils/mod.rs | 1 + bin/src/utils/paa/inspect.rs | 63 + bin/src/utils/paa/mod.rs | 9 +- bin/src/utils/pbo/inspect.rs | 85 + bin/src/utils/pbo/mod.rs | 7 +- book/SUMMARY.md | 11 +- book/commands/check.md | 19 + book/commands/localization/coverage.md | 21 + book/commands/localization/sort.md | 26 + book/utilities/config/inspect.md | 24 + book/utilities/paa/convert.md | 23 + book/utilities/paa/inspect.md | 24 + libs/config/src/lib.rs | 2 +- libs/stringtable/Cargo.toml | 18 + libs/stringtable/src/key.rs | 183 +++ libs/stringtable/src/lib.rs | 59 + libs/stringtable/src/package.rs | 123 ++ libs/stringtable/src/totals.rs | 107 ++ libs/stringtable/tests/ace_arsenal.rs | 18 + libs/stringtable/tests/ace_arsenal.xml | 1740 +++++++++++++++++++++ 32 files changed, 2944 insertions(+), 153 deletions(-) create mode 100644 bin/src/commands/localization/coverage.rs create mode 100644 bin/src/commands/localization/mod.rs create mode 100644 bin/src/commands/localization/sort.rs create mode 100644 bin/src/utils/config/inspect.rs create mode 100644 bin/src/utils/config/mod.rs create mode 100644 bin/src/utils/paa/inspect.rs create mode 100644 bin/src/utils/pbo/inspect.rs create mode 100644 book/commands/check.md create mode 100644 book/commands/localization/coverage.md create mode 100644 book/commands/localization/sort.md create mode 100644 book/utilities/config/inspect.md create mode 100644 book/utilities/paa/convert.md create mode 100644 book/utilities/paa/inspect.md create mode 100644 libs/stringtable/Cargo.toml create mode 100644 libs/stringtable/src/key.rs create mode 100644 libs/stringtable/src/lib.rs create mode 100644 libs/stringtable/src/package.rs create mode 100644 libs/stringtable/src/totals.rs create mode 100644 libs/stringtable/tests/ace_arsenal.rs create mode 100644 libs/stringtable/tests/ace_arsenal.xml diff --git a/Cargo.lock b/Cargo.lock index 66a01837..8691c866 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1414,6 +1414,7 @@ dependencies = [ "hemtt-preprocessor", "hemtt-signing", "hemtt-sqf", + "hemtt-stringtable", "hemtt-workspace", "num_cpus", "paste", @@ -1605,6 +1606,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "hemtt-stringtable" +version = "1.0.0" +dependencies = [ + "indexmap", + "insta", + "paste", + "quick-xml", + "serde", +] + [[package]] name = "hemtt-workspace" version = "1.0.0" @@ -3157,6 +3169,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quote" version = "1.0.37" diff --git a/Cargo.toml b/Cargo.toml index 26c8ce5a..c59c67f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "libs/preprocessor", "libs/signing", "libs/sqf", + "libs/stringtable", "libs/workspace", ] resolver = "2" diff --git a/bin/Cargo.toml b/bin/Cargo.toml index d87a0c53..8be91117 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -28,6 +28,7 @@ hemtt-pbo = { path = "../libs/pbo", version = "1.0.1" } hemtt-preprocessor = { path = "../libs/preprocessor", version = "1.0.0" } hemtt-signing = { path = "../libs/signing", version = "1.0.0" } hemtt-sqf = { path = "../libs/sqf", version = "1.0.0" } +hemtt-stringtable = { path = "../libs/stringtable", version = "1.0.0" } hemtt-workspace = { path = "../libs/workspace", version = "1.0.0" } arma3-wiki = { workspace = true } @@ -38,6 +39,7 @@ fs_extra = "1.3.0" git2 = { workspace = true } glob = "0.3.1" num_cpus = "1.16.0" +paste = { workspace = true } rayon = "1.10.0" regex = { workspace = true } reqwest = { version = "0.12.8", features = ["blocking", "json"] } @@ -62,5 +64,4 @@ enable-ansi-support = "0.2.1" winreg = "0.52.0" [dev-dependencies] -paste = { workspace = true } sealed_test = "1.1.0" diff --git a/bin/src/commands/localization/coverage.rs b/bin/src/commands/localization/coverage.rs new file mode 100644 index 00000000..a4bef01c --- /dev/null +++ b/bin/src/commands/localization/coverage.rs @@ -0,0 +1,152 @@ +use std::{collections::HashMap, io::BufReader}; + +use hemtt_stringtable::{Project, Totals}; +use term_table::{ + row::Row, + table_cell::{Alignment, TableCell}, + Table, TableStyle, +}; + +use crate::{context::Context, report::Report, Error}; + +macro_rules! missing { + ($totals:ident, $missing:ident, $lang:tt, $addon:expr) => { + paste::paste! { + if $totals.$lang() != $totals.total() { + $missing.entry(stringify!($lang)).or_insert_with(Vec::new).push($addon); + } + } + }; +} + +fn first_capital(s: &str) -> String { + let mut c = s.chars(); + c.next().map_or_else(String::new, |f| { + f.to_uppercase().collect::() + c.as_str() + }) +} + +macro_rules! row { + ($table:ident, $global:ident, $missing:ident, $lang:tt) => { + if $global.$lang() != 0 { + $table.add_row(Row::new(vec![ + TableCell::new(first_capital(stringify!($lang))), + TableCell::new(format!( + "{:.2}%", + f64::from($global.$lang()) / f64::from($global.total()) * 100.0 + )), + TableCell::new($global.$lang()), + { + if let Some(missing) = $missing.get(stringify!($lang)) { + if missing.len() < 3 { + TableCell::new(missing.join(", ")) + } else { + TableCell::new(format!("{} and {} more", missing[0], missing.len() - 1)) + } + } else { + TableCell::new("") + } + }, + ])); + } + }; +} + +pub fn coverage() -> Result { + let ctx = Context::new(None, crate::context::PreservePrevious::Remove, true)?; + + let mut global = Totals::default(); + let mut missing = HashMap::new(); + + for addon in ctx.addons() { + let stringtable_path = ctx + .workspace_path() + .join(addon.folder())? + .join("stringtable.xml")?; + if stringtable_path.exists()? { + let project = match Project::from_reader(BufReader::new(stringtable_path.open_file()?)) + { + Ok(project) => project, + Err(e) => { + error!("Failed to read stringtable for {}", addon.folder()); + error!("{:?}", e); + return Ok(Report::new()); + } + }; + project.packages().iter().for_each(|package| { + let totals = package.totals(); + missing!(totals, missing, original, addon.folder()); + missing!(totals, missing, english, addon.folder()); + missing!(totals, missing, czech, addon.folder()); + missing!(totals, missing, french, addon.folder()); + missing!(totals, missing, spanish, addon.folder()); + missing!(totals, missing, italian, addon.folder()); + missing!(totals, missing, polish, addon.folder()); + missing!(totals, missing, portuguese, addon.folder()); + missing!(totals, missing, russian, addon.folder()); + missing!(totals, missing, german, addon.folder()); + missing!(totals, missing, korean, addon.folder()); + missing!(totals, missing, japanese, addon.folder()); + missing!(totals, missing, chinese, addon.folder()); + missing!(totals, missing, chinesesimp, addon.folder()); + missing!(totals, missing, turkish, addon.folder()); + missing!(totals, missing, swedish, addon.folder()); + missing!(totals, missing, slovak, addon.folder()); + missing!(totals, missing, serbocroatian, addon.folder()); + missing!(totals, missing, norwegian, addon.folder()); + missing!(totals, missing, icelandic, addon.folder()); + missing!(totals, missing, hungarian, addon.folder()); + missing!(totals, missing, greek, addon.folder()); + missing!(totals, missing, finnish, addon.folder()); + missing!(totals, missing, dutch, addon.folder()); + global.merge(&totals); + }); + } + } + + let mut table = Table::new(); + table.style = TableStyle::thin(); + table.add_row(Row::new(vec![ + TableCell::builder("Language") + .alignment(Alignment::Center) + .build(), + TableCell::builder("Percent") + .alignment(Alignment::Center) + .build(), + TableCell::builder("Total") + .alignment(Alignment::Center) + .build(), + TableCell::builder("Missing Addons") + .alignment(Alignment::Center) + .build(), + ])); + + row!(table, global, missing, original); + row!(table, global, missing, english); + row!(table, global, missing, czech); + row!(table, global, missing, french); + row!(table, global, missing, spanish); + row!(table, global, missing, italian); + row!(table, global, missing, polish); + row!(table, global, missing, portuguese); + row!(table, global, missing, russian); + row!(table, global, missing, german); + row!(table, global, missing, korean); + row!(table, global, missing, japanese); + row!(table, global, missing, chinese); + row!(table, global, missing, chinesesimp); + row!(table, global, missing, turkish); + row!(table, global, missing, swedish); + row!(table, global, missing, slovak); + row!(table, global, missing, serbocroatian); + row!(table, global, missing, norwegian); + row!(table, global, missing, icelandic); + row!(table, global, missing, hungarian); + row!(table, global, missing, greek); + row!(table, global, missing, finnish); + row!(table, global, missing, dutch); + + println!("{}", table.render()); + + Ok(Report::new()) +} diff --git a/bin/src/commands/localization/mod.rs b/bin/src/commands/localization/mod.rs new file mode 100644 index 00000000..d2c70628 --- /dev/null +++ b/bin/src/commands/localization/mod.rs @@ -0,0 +1,33 @@ +use clap::{ArgMatches, Command}; + +use crate::{report::Report, Error}; + +mod coverage; +mod sort; + +#[must_use] +pub fn cli() -> Command { + Command::new("localization") + .visible_alias("ln") + .about("Manage localization stringtables") + .subcommand(Command::new("coverage").about("Check the coverage of localization")) + .subcommand(Command::new("sort").about("Sort the stringtables")) +} + +/// Execute the localization command +/// +/// # Errors +/// [`Error`] depending on the modules +/// +/// # Panics +/// If a name is not provided, but this is usually handled by clap +pub fn execute(matches: &ArgMatches) -> Result { + match matches.subcommand() { + Some(("coverage", _)) => coverage::coverage(), + Some(("sort", _)) => sort::sort(), + _ => { + cli().print_help().expect("Failed to print help"); + Ok(Report::new()) + } + } +} diff --git a/bin/src/commands/localization/sort.rs b/bin/src/commands/localization/sort.rs new file mode 100644 index 00000000..ff312a26 --- /dev/null +++ b/bin/src/commands/localization/sort.rs @@ -0,0 +1,44 @@ +use std::io::BufReader; + +use hemtt_stringtable::Project; + +use crate::{context::Context, report::Report, Error}; + +pub fn sort() -> Result { + let ctx = Context::new(None, crate::context::PreservePrevious::Remove, true)?; + + for addon in ctx.addons() { + let stringtable_path = ctx + .workspace_path() + .join(addon.folder())? + .join("stringtable.xml")?; + if stringtable_path.exists()? { + match Project::from_reader(BufReader::new(stringtable_path.open_file()?)) { + Ok(mut project) => { + project.sort(); + let out_path = ctx + .project_folder() + .join(addon.folder_pathbuf()) + .join("stringtable.xml"); + let mut writer = String::new(); + if let Err(e) = project.to_writer(&mut writer) { + error!("Failed to write stringtable for {}", addon.folder()); + error!("{:?}", e); + return Ok(Report::new()); + } + if let Err(e) = std::fs::write(out_path, writer) { + error!("Failed to write stringtable for {}", addon.folder()); + error!("{:?}", e); + return Ok(Report::new()); + } + } + Err(e) => { + error!("Failed to read stringtable for {}", addon.folder()); + error!("{:?}", e); + return Ok(Report::new()); + } + }; + } + } + Ok(Report::new()) +} diff --git a/bin/src/commands/mod.rs b/bin/src/commands/mod.rs index 11340d13..8e6581af 100644 --- a/bin/src/commands/mod.rs +++ b/bin/src/commands/mod.rs @@ -3,6 +3,7 @@ pub mod build; pub mod check; pub mod dev; pub mod launch; +pub mod localization; pub mod new; pub mod release; pub mod script; diff --git a/bin/src/commands/utils.rs b/bin/src/commands/utils.rs index 42da02c7..e6316342 100644 --- a/bin/src/commands/utils.rs +++ b/bin/src/commands/utils.rs @@ -8,6 +8,7 @@ pub fn cli() -> Command { .about("Use HEMTT standalone utils") .subcommand_required(false) .arg_required_else_help(true) + .subcommand(utils::config::cli()) .subcommand(utils::inspect::cli()) .subcommand(utils::paa::cli()) .subcommand(utils::pbo::cli()) @@ -21,6 +22,7 @@ pub fn cli() -> Command { /// [`Error`] depending on the modules pub fn execute(matches: &ArgMatches) -> Result { match matches.subcommand() { + Some(("config", matches)) => utils::config::execute(matches), Some(("inspect", matches)) => utils::inspect::execute(matches), Some(("paa", matches)) => utils::paa::execute(matches), Some(("pbo", matches)) => utils::pbo::execute(matches), diff --git a/bin/src/lib.rs b/bin/src/lib.rs index 2a2b18eb..c4c88a23 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -26,6 +26,7 @@ pub fn cli() -> Command { .subcommand(commands::book::cli()) .subcommand(commands::new::cli()) .subcommand(commands::check::cli()) + .subcommand(commands::localization::cli()) .subcommand(commands::dev::cli()) .subcommand(commands::launch::cli()) .subcommand(commands::build::cli()) @@ -138,6 +139,9 @@ pub fn execute(matches: &ArgMatches) -> Result<(), Error> { Some(("new", matches)) => commands::new::execute(matches).map(Some), Some(("dev", matches)) => commands::dev::execute(matches, &[]).map(Some), Some(("check", _matches)) => commands::check::execute().map(Some), + Some(("localization", matches)) => commands::localization::execute(matches) + .map_err(std::convert::Into::into) + .map(Some), Some(("build", matches)) => commands::build::execute(matches) .map_err(std::convert::Into::into) .map(Some), @@ -166,10 +170,9 @@ pub fn execute(matches: &ArgMatches) -> Result<(), Error> { }; if let Some(report) = report? { report.write_to_stdout(); - if !matches - .subcommand_name() - .is_some_and(|s| s == "new" || s == "utils" || s == "wiki" || s == "book") - { + if !matches.subcommand_name().is_some_and(|s| { + s == "new" || s == "utils" || s == "wiki" || s == "book" || s == "localization" + }) { report.write_ci_annotations()?; } if report.failed() { diff --git a/bin/src/utils/config/inspect.rs b/bin/src/utils/config/inspect.rs new file mode 100644 index 00000000..83f537cd --- /dev/null +++ b/bin/src/utils/config/inspect.rs @@ -0,0 +1,79 @@ +use std::{path::PathBuf, sync::Arc}; + +use hemtt_common::config::ProjectConfig; +use hemtt_config::ConfigReport; +use hemtt_preprocessor::Processor; +use hemtt_workspace::{ + reporting::{Code, WorkspaceFiles}, + LayerType, +}; + +use crate::Error; + +/// Prints information about a config to stdout +/// +/// # Errors +/// [`Error::Preprocessor`] if the file can not be preprocessed +pub fn inspect(file: &PathBuf) -> Result<(), Error> { + let report = get_report(file)?; + let workspacefiles = WorkspaceFiles::new(); + match report { + Ok(report) => { + println!("Config is valid!"); + if report.patches().is_empty() { + println!(" - Contains no CfgPatches"); + } else { + println!(" - Contains the following CfgPatches:"); + for patch in report.patches() { + println!( + " - {}, requires {}", + patch.name().as_str(), + patch.required_version() + ); + } + } + for code in report.codes() { + if let Some(diag) = code.diagnostic() { + eprintln!("{}", diag.to_string(&workspacefiles)); + } + } + Ok(()) + } + Err(errors) => { + for error in errors { + if let Some(diag) = error.diagnostic() { + eprintln!("{}", diag.to_string(&workspacefiles)); + } + } + Ok(()) + } + } +} + +pub fn get_report(file: &PathBuf) -> Result>>, Error> { + assert!(file.is_file()); + let folder = PathBuf::from(&file) + .parent() + .expect("File has no parent") + .to_path_buf(); + let workspace = hemtt_workspace::Workspace::builder() + .physical(&folder, LayerType::Source) + .finish( + Some(ProjectConfig::test_project()), + false, + &hemtt_common::config::PDriveOption::Disallow, + )?; + let source = workspace + .join( + file.file_name() + .expect("has a filename") + .to_str() + .expect("valid utf-8"), + ) + .expect("File is valid"); + let processed = Processor::run(&source).map_err(|e| e.1)?; + Ok(hemtt_config::parse( + Some(&ProjectConfig::test_project()), + &processed, + )) +} diff --git a/bin/src/utils/config/mod.rs b/bin/src/utils/config/mod.rs new file mode 100644 index 00000000..a77070b2 --- /dev/null +++ b/bin/src/utils/config/mod.rs @@ -0,0 +1,40 @@ +use std::path::PathBuf; + +use clap::{ArgMatches, Command}; + +use crate::Error; + +mod inspect; + +pub use inspect::inspect; + +#[must_use] +pub fn cli() -> Command { + Command::new("paa") + .about("Commands for PAA files") + .arg_required_else_help(true) + .subcommand( + Command::new("inspect").about("Inspect a config file").arg( + clap::Arg::new("config") + .help("Config to inspect") + .required(true), + ), + ) +} + +/// Execute the config command +/// +/// # Errors +/// [`Error`] depending on the modules +/// +/// # Panics +/// If the args are not present from clap +pub fn execute(matches: &ArgMatches) -> Result<(), Error> { + match matches.subcommand() { + Some(("inspect", matches)) => inspect::inspect(&PathBuf::from( + matches.get_one::("config").expect("required"), + )), + + _ => unreachable!(), + } +} diff --git a/bin/src/utils/inspect.rs b/bin/src/utils/inspect.rs index d7861d53..b398b4f2 100644 --- a/bin/src/utils/inspect.rs +++ b/bin/src/utils/inspect.rs @@ -5,13 +5,7 @@ use std::{ }; use clap::{ArgMatches, Command}; -use hemtt_pbo::ReadablePbo; use hemtt_signing::{BIPublicKey, BISign}; -use term_table::{ - row::Row, - table_cell::{Alignment, TableCell}, - Table, TableStyle, -}; use crate::Error; @@ -43,10 +37,10 @@ pub fn execute(matches: &ArgMatches) -> Result<(), Error> { .unwrap_or_default() { "paa" => { - paa(File::open(&path)?)?; + super::paa::inspect(File::open(&path)?)?; } "pbo" => { - pbo(File::open(&path)?)?; + super::pbo::inspect(File::open(&path)?)?; } "bikey" => { bikey(File::open(&path)?, &path)?; @@ -54,6 +48,9 @@ pub fn execute(matches: &ArgMatches) -> Result<(), Error> { "bisign" => { bisign(File::open(&path)?, &path)?; } + "cpp" | "hpp" => { + super::config::inspect(&path)?; + } _ => { let mut file = File::open(&path)?; let buf = &mut [0u8; 6]; @@ -62,14 +59,14 @@ pub fn execute(matches: &ArgMatches) -> Result<(), Error> { // PBO if buf == b"\x00sreV\x00" { warn!("The file appears to be a PBO but does not have the .pbo extension."); - pbo(file)?; + super::pbo::inspect(file)?; return Ok(()); } // PAA (skip first two bytes) if &buf[2..] == b"GGAT" { warn!("The file appears to be a PAA but does not have the .paa extension."); file.seek(std::io::SeekFrom::Start(0))?; - paa(file)?; + super::paa::inspect(file)?; return Ok(()); } // BiSign @@ -123,132 +120,3 @@ pub fn bisign(mut file: File, path: &PathBuf) -> Result { println!(" - Modulus: {}", signature.modulus_display(13)); Ok(signature) } - -/// Prints information about a [`ReadablePbo`] to stdout -/// -/// # Errors -/// [`hemtt_pbo::Error`] if the file is not a valid [`ReadablePbo`] -/// -/// # Panics -/// If the file is not a valid [`ReadablePbo`] -pub fn pbo(file: File) -> Result<(), Error> { - let mut pbo = ReadablePbo::from(file)?; - println!("Properties"); - for (key, value) in pbo.properties() { - println!(" - {key}: {value}"); - } - println!("Checksum (SHA1)"); - let stored = *pbo.checksum(); - println!(" - Stored: {}", stored.hex()); - let actual = pbo.gen_checksum()?; - println!(" - Actual: {}", actual.hex()); - - let files = pbo.files(); - println!("Files"); - if pbo.is_sorted().is_ok() { - println!(" - Sorted: true"); - } else { - println!(" - Sorted: false !!!"); - } - println!(" - Count: {}", files.len()); - let mut table = Table::new(); - table.style = TableStyle::thin(); - table.add_row(Row::new(vec![ - TableCell::builder("Filename") - .alignment(Alignment::Center) - .build(), - TableCell::builder("Method") - .alignment(Alignment::Center) - .build(), - TableCell::builder("Size") - .alignment(Alignment::Center) - .build(), - TableCell::builder("Original") - .alignment(Alignment::Center) - .build(), - TableCell::builder("Timestamp") - .alignment(Alignment::Center) - .build(), - ])); - for file in files { - let mut row = Row::new(vec![ - TableCell::new(file.filename()), - TableCell::builder(file.mime().to_string()) - .alignment(Alignment::Right) - .build(), - TableCell::builder(file.size().to_string()) - .alignment(Alignment::Right) - .build(), - TableCell::builder(file.original().to_string()) - .alignment(Alignment::Right) - .build(), - TableCell::builder(file.timestamp().to_string()) - .alignment(Alignment::Right) - .build(), - ]); - row.has_separator = table.rows.len() == 1; - table.add_row(row); - } - println!("{}", table.render()); - if pbo.is_sorted().is_err() { - warn!("The PBO is not sorted, signatures may be invalid"); - } - if stored != actual { - warn!("The PBO has an invalid hash stored"); - } - Ok(()) -} - -/// Prints information about a PAA to stdout -/// -/// # Errors -/// [`Error::Io`] if the file is not a valid [`hemtt_paa::Paa`] -pub fn paa(mut file: File) -> Result<(), Error> { - let paa = hemtt_paa::Paa::read(&mut file)?; - println!("PAA"); - println!(" - Format: {}", paa.format()); - let maps = paa.maps(); - println!("Maps: {}", maps.len()); - let mut table = Table::new(); - table.style = TableStyle::thin(); - table.add_row(Row::new(vec![ - TableCell::builder("Width") - .alignment(Alignment::Center) - .build(), - TableCell::builder("Height") - .alignment(Alignment::Center) - .build(), - TableCell::builder("Size") - .alignment(Alignment::Center) - .build(), - TableCell::builder("Format") - .alignment(Alignment::Center) - .build(), - TableCell::builder("Compressed") - .alignment(Alignment::Center) - .build(), - ])); - for map in maps { - let mut row = Row::new(vec![ - TableCell::builder(map.width().to_string()) - .alignment(Alignment::Right) - .build(), - TableCell::builder(map.height().to_string()) - .alignment(Alignment::Right) - .build(), - TableCell::builder(map.data().len().to_string()) - .alignment(Alignment::Right) - .build(), - TableCell::builder(format!("{:?}", map.format())) - .alignment(Alignment::Right) - .build(), - TableCell::builder(map.is_compressed().to_string()) - .alignment(Alignment::Right) - .build(), - ]); - row.has_separator = table.rows.len() == 1; - table.add_row(row); - } - println!("{}", table.render()); - Ok(()) -} diff --git a/bin/src/utils/mod.rs b/bin/src/utils/mod.rs index ae81043a..550790f5 100644 --- a/bin/src/utils/mod.rs +++ b/bin/src/utils/mod.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod inspect; pub mod paa; pub mod pbo; diff --git a/bin/src/utils/paa/inspect.rs b/bin/src/utils/paa/inspect.rs new file mode 100644 index 00000000..fd4e97b2 --- /dev/null +++ b/bin/src/utils/paa/inspect.rs @@ -0,0 +1,63 @@ +use std::fs::File; + +use term_table::{ + row::Row, + table_cell::{Alignment, TableCell}, + Table, TableStyle, +}; + +use crate::Error; + +/// Prints information about a PAA to stdout +/// +/// # Errors +/// [`Error::Io`] if the file is not a valid [`hemtt_paa::Paa`] +pub fn inspect(mut file: File) -> Result<(), Error> { + let paa = hemtt_paa::Paa::read(&mut file)?; + println!("PAA"); + println!(" - Format: {}", paa.format()); + let maps = paa.maps(); + println!("Maps: {}", maps.len()); + let mut table = Table::new(); + table.style = TableStyle::thin(); + table.add_row(Row::new(vec![ + TableCell::builder("Width") + .alignment(Alignment::Center) + .build(), + TableCell::builder("Height") + .alignment(Alignment::Center) + .build(), + TableCell::builder("Size") + .alignment(Alignment::Center) + .build(), + TableCell::builder("Format") + .alignment(Alignment::Center) + .build(), + TableCell::builder("Compressed") + .alignment(Alignment::Center) + .build(), + ])); + for map in maps { + let mut row = Row::new(vec![ + TableCell::builder(map.width().to_string()) + .alignment(Alignment::Right) + .build(), + TableCell::builder(map.height().to_string()) + .alignment(Alignment::Right) + .build(), + TableCell::builder(map.data().len().to_string()) + .alignment(Alignment::Right) + .build(), + TableCell::builder(format!("{:?}", map.format())) + .alignment(Alignment::Right) + .build(), + TableCell::builder(map.is_compressed().to_string()) + .alignment(Alignment::Right) + .build(), + ]); + row.has_separator = table.rows.len() == 1; + table.add_row(row); + } + println!("{}", table.render()); + Ok(()) +} diff --git a/bin/src/utils/paa/mod.rs b/bin/src/utils/paa/mod.rs index 33a298d4..1b12b36b 100644 --- a/bin/src/utils/paa/mod.rs +++ b/bin/src/utils/paa/mod.rs @@ -1,12 +1,13 @@ -mod convert; - use std::{fs::File, path::PathBuf}; use clap::{ArgMatches, Command}; use crate::Error; -use super::inspect::paa; +mod convert; +mod inspect; + +pub use inspect::inspect; #[must_use] pub fn cli() -> Command { @@ -32,7 +33,7 @@ pub fn execute(matches: &ArgMatches) -> Result<(), Error> { match matches.subcommand() { Some(("convert", matches)) => convert::execute(matches), - Some(("inspect", matches)) => paa(File::open(PathBuf::from( + Some(("inspect", matches)) => inspect::inspect(File::open(PathBuf::from( matches.get_one::("paa").expect("required"), ))?), diff --git a/bin/src/utils/pbo/inspect.rs b/bin/src/utils/pbo/inspect.rs new file mode 100644 index 00000000..81701321 --- /dev/null +++ b/bin/src/utils/pbo/inspect.rs @@ -0,0 +1,85 @@ +use std::fs::File; + +use hemtt_pbo::ReadablePbo; +use term_table::{ + row::Row, + table_cell::{Alignment, TableCell}, + Table, TableStyle, +}; + +use crate::Error; + +/// Prints information about a [`ReadablePbo`] to stdout +/// +/// # Errors +/// [`hemtt_pbo::Error`] if the file is not a valid [`ReadablePbo`] +/// +/// # Panics +/// If the file is not a valid [`ReadablePbo`] +pub fn inspect(file: File) -> Result<(), Error> { + let mut pbo = ReadablePbo::from(file)?; + println!("Properties"); + for (key, value) in pbo.properties() { + println!(" - {key}: {value}"); + } + println!("Checksum (SHA1)"); + let stored = *pbo.checksum(); + println!(" - Stored: {}", stored.hex()); + let actual = pbo.gen_checksum()?; + println!(" - Actual: {}", actual.hex()); + + let files = pbo.files(); + println!("Files"); + if pbo.is_sorted().is_ok() { + println!(" - Sorted: true"); + } else { + println!(" - Sorted: false !!!"); + } + println!(" - Count: {}", files.len()); + let mut table = Table::new(); + table.style = TableStyle::thin(); + table.add_row(Row::new(vec![ + TableCell::builder("Filename") + .alignment(Alignment::Center) + .build(), + TableCell::builder("Method") + .alignment(Alignment::Center) + .build(), + TableCell::builder("Size") + .alignment(Alignment::Center) + .build(), + TableCell::builder("Original") + .alignment(Alignment::Center) + .build(), + TableCell::builder("Timestamp") + .alignment(Alignment::Center) + .build(), + ])); + for file in files { + let mut row = Row::new(vec![ + TableCell::new(file.filename()), + TableCell::builder(file.mime().to_string()) + .alignment(Alignment::Right) + .build(), + TableCell::builder(file.size().to_string()) + .alignment(Alignment::Right) + .build(), + TableCell::builder(file.original().to_string()) + .alignment(Alignment::Right) + .build(), + TableCell::builder(file.timestamp().to_string()) + .alignment(Alignment::Right) + .build(), + ]); + row.has_separator = table.rows.len() == 1; + table.add_row(row); + } + println!("{}", table.render()); + if pbo.is_sorted().is_err() { + warn!("The PBO is not sorted, signatures may be invalid"); + } + if stored != actual { + warn!("The PBO has an invalid hash stored"); + } + Ok(()) +} diff --git a/bin/src/utils/pbo/mod.rs b/bin/src/utils/pbo/mod.rs index b8ce31d8..72ccd5fb 100644 --- a/bin/src/utils/pbo/mod.rs +++ b/bin/src/utils/pbo/mod.rs @@ -4,11 +4,12 @@ use clap::{ArgMatches, Command}; use crate::Error; -use super::inspect::pbo; - mod extract; +mod inspect; mod unpack; +pub use inspect::inspect; + #[must_use] pub fn cli() -> Command { Command::new("pbo") @@ -35,7 +36,7 @@ pub fn execute(matches: &ArgMatches) -> Result<(), Error> { Some(("extract", matches)) => extract::execute(matches), Some(("unpack", matches)) => unpack::execute(matches), - Some(("inspect", matches)) => pbo(File::open(PathBuf::from( + Some(("inspect", matches)) => inspect::inspect(File::open(PathBuf::from( matches.get_one::("pbo").expect("required"), ))?), diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 6ca2ab64..696af693 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -11,10 +11,14 @@ - [Minimum](configuration/minimum.md) - [Version](configuration/version.md) - [Lints](configuration/lints.md) - - [Addon](configuration/addon.md)] + - [Addon](configuration/addon.md) - [P Drive](configuration/p-drive.md) - [Custom Commands](configuration/custom-commands.md) - [Commands](commands/index.md) + - [check](commands/check.md) + - [localization]() + - [coverage](commands/localization/coverage.md) + - [sort](commands/localization/sort.md) - [new](commands/new.md) - [dev](commands/dev.md) - [launch](commands/launch.md) @@ -40,8 +44,13 @@ - [Inspect](utilities/pbo/inspect.md) - [Extract](utilities/pbo/extract.md) - [Unpack](utilities/pbo/unpack.md) +- [PAA]() + - [Inspect](utilities/paa/inspect.md) + - [Convert](utilities/paa/convert.md) - [SQF]() - [Case](utilities/sqf/case.md) +- [Config]() + - [Inspect](utilities/config/inspect.md) - [Verify](utilities/signing/verify.md) # Reference diff --git a/book/commands/check.md b/book/commands/check.md new file mode 100644 index 00000000..b68f91f5 --- /dev/null +++ b/book/commands/check.md @@ -0,0 +1,19 @@ +# hemtt check + +
Check your project
+
+Usage: hemtt check [OPTIONS]
+
+Options:
+    -t, --threads <threads>
+        Number of threads, defaults to # of CPUs
+
+    -v...
+        Verbosity level
+
+    -h, --help
+        Print help information (use `-h` for a summary)
+
+
+ +`hemtt check` is the quickest way to check your project for errors. All the same checks are run as [`hemtt dev`](./dev), but it will not write files to disk, saving time and resources. diff --git a/book/commands/localization/coverage.md b/book/commands/localization/coverage.md new file mode 100644 index 00000000..a5415ed6 --- /dev/null +++ b/book/commands/localization/coverage.md @@ -0,0 +1,21 @@ +# hemtt localization coverage + +
Check the coverage of the stringtable.xml files in the project.
+
+Usage: hemtt localization coverage [OPTIONS]
+
+Options:
+    -t, --threads <threads>
+        Number of threads, defaults to # of CPUs
+
+    -v...
+        Verbosity level
+
+    -h, --help
+        Print help information (use `-h` for a summary)
+
+
+ +## Description + +HEMTT will display a table of the coverage of language localization in the project. Showing the percentage, total strings, and how many addons have gaps in their localization. diff --git a/book/commands/localization/sort.md b/book/commands/localization/sort.md new file mode 100644 index 00000000..aa4db5d5 --- /dev/null +++ b/book/commands/localization/sort.md @@ -0,0 +1,26 @@ +# hemtt localization sort + +
Sorts the stringtable.xml files in the project.
+
+Usage: hemtt localization sort [OPTIONS]
+
+Options:
+    -t, --threads <threads>
+        Number of threads, defaults to # of CPUs
+
+    -v...
+        Verbosity level
+
+    -h, --help
+        Print help information (use `-h` for a summary)
+
+
+ +## Description + +HEMTT will: + +1. Sort the Packages in alphabetical order. +2. Sort the Containers in alphabetical order (if any). +3. Sort the Keys in alphabetical order. +4. Sort the Localized Strings in the order of [this table](https://community.bistudio.com/wiki/Stringtable.xml#Supported_Languages) diff --git a/book/utilities/config/inspect.md b/book/utilities/config/inspect.md new file mode 100644 index 00000000..ac81ad74 --- /dev/null +++ b/book/utilities/config/inspect.md @@ -0,0 +1,24 @@ +# hemtt utils config inspect + +
Inspect a Config
+
+Usage: hemtt utils config inspect [OPTIONS] <config>
+
+Arguments:
+  <config>
+        Config to inspect
+
+Options:
+  -v...
+        Verbosity level
+
+  -h, --help
+        Print help (see a summary with '-h')
+
+
+ +Provides information about a Config. + +This is the same as `hemtt utils inspect` but will assume the file is a Config. + +In some cases the output might be cut off in the terminal. Adjust the `terminal.integrated.scrollback` setting in VS Code if necessary. diff --git a/book/utilities/paa/convert.md b/book/utilities/paa/convert.md new file mode 100644 index 00000000..1551b59f --- /dev/null +++ b/book/utilities/paa/convert.md @@ -0,0 +1,23 @@ +# hemtt utils pbo convert + +
Convert a PAA to another format
+
+Usage: hemtt utils paa inspect [OPTIONS] <paa> <output>
+
+Arguments:
+  <paa>
+        PAA to convert
+
+  <output>
+        Output file
+
+Options:
+  -v...
+        Verbosity level
+
+  -h, --help
+        Print help (see a summary with '-h')
+
+
+ +Converts a PAA to another image format. diff --git a/book/utilities/paa/inspect.md b/book/utilities/paa/inspect.md new file mode 100644 index 00000000..6a4aa852 --- /dev/null +++ b/book/utilities/paa/inspect.md @@ -0,0 +1,24 @@ +# hemtt utils pbo inspect + +
Inspect a PAA
+
+Usage: hemtt utils paa inspect [OPTIONS] <paa>
+
+Arguments:
+  <paa>
+        PAA to inspect
+
+Options:
+  -v...
+        Verbosity level
+
+  -h, --help
+        Print help (see a summary with '-h')
+
+
+ +Provides information about a PAA. + +This is the same as `hemtt utils inspect` but will assume the file is a PAA. + +In some cases the output might be cut off in the terminal. Adjust the `terminal.integrated.scrollback` setting in VS Code if necessary. diff --git a/libs/config/src/lib.rs b/libs/config/src/lib.rs index 32bf1ee6..2d6ddaa5 100644 --- a/libs/config/src/lib.rs +++ b/libs/config/src/lib.rs @@ -8,9 +8,9 @@ use std::sync::Arc; mod analyze; mod model; -pub use model::*; pub mod parse; pub mod rapify; +pub use model::*; pub use analyze::CONFIG_LINTS; diff --git a/libs/stringtable/Cargo.toml b/libs/stringtable/Cargo.toml new file mode 100644 index 00000000..a9348628 --- /dev/null +++ b/libs/stringtable/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "hemtt-stringtable" +version = "1.0.0" +edition = "2021" +description = "A stringtable library for hemtt" +license = "GPL-2.0" + +[lints] +workspace = true + +[dependencies] +indexmap = { workspace = true } +paste = { workspace = true } +quick-xml = { version = "0.36.2", features = ["serialize"] } +serde = { workspace = true, features = ["derive"] } + +[dev-dependencies] +insta = { workspace = true } diff --git a/libs/stringtable/src/key.rs b/libs/stringtable/src/key.rs new file mode 100644 index 00000000..1e3e78db --- /dev/null +++ b/libs/stringtable/src/key.rs @@ -0,0 +1,183 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct Key { + #[serde(rename = "@ID")] + id: String, + #[serde(skip_serializing_if = "Option::is_none")] + original: Option, + #[serde(skip_serializing_if = "Option::is_none")] + english: Option, + #[serde(skip_serializing_if = "Option::is_none")] + czech: Option, + #[serde(skip_serializing_if = "Option::is_none")] + french: Option, + #[serde(skip_serializing_if = "Option::is_none")] + spanish: Option, + #[serde(skip_serializing_if = "Option::is_none")] + italian: Option, + #[serde(skip_serializing_if = "Option::is_none")] + polish: Option, + #[serde(skip_serializing_if = "Option::is_none")] + portuguese: Option, + #[serde(skip_serializing_if = "Option::is_none")] + russian: Option, + #[serde(skip_serializing_if = "Option::is_none")] + german: Option, + #[serde(skip_serializing_if = "Option::is_none")] + korean: Option, + #[serde(skip_serializing_if = "Option::is_none")] + japanese: Option, + #[serde(skip_serializing_if = "Option::is_none")] + chinese: Option, + #[serde(skip_serializing_if = "Option::is_none")] + chinesesimp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + turkish: Option, + #[serde(skip_serializing_if = "Option::is_none")] + swedish: Option, + #[serde(skip_serializing_if = "Option::is_none")] + slovak: Option, + #[serde(skip_serializing_if = "Option::is_none")] + serbocroatian: Option, + #[serde(skip_serializing_if = "Option::is_none")] + norwegian: Option, + #[serde(skip_serializing_if = "Option::is_none")] + icelandic: Option, + #[serde(skip_serializing_if = "Option::is_none")] + hungarian: Option, + #[serde(skip_serializing_if = "Option::is_none")] + greek: Option, + #[serde(skip_serializing_if = "Option::is_none")] + finnish: Option, + #[serde(skip_serializing_if = "Option::is_none")] + dutch: Option, +} + +impl Key { + #[must_use] + pub fn id(&self) -> &str { + &self.id + } + + #[must_use] + pub fn original(&self) -> Option<&str> { + self.original.as_deref() + } + + #[must_use] + pub fn english(&self) -> Option<&str> { + self.english.as_deref() + } + + #[must_use] + pub fn czech(&self) -> Option<&str> { + self.czech.as_deref() + } + + #[must_use] + pub fn french(&self) -> Option<&str> { + self.french.as_deref() + } + + #[must_use] + pub fn spanish(&self) -> Option<&str> { + self.spanish.as_deref() + } + + #[must_use] + pub fn italian(&self) -> Option<&str> { + self.italian.as_deref() + } + + #[must_use] + pub fn polish(&self) -> Option<&str> { + self.polish.as_deref() + } + + #[must_use] + pub fn portuguese(&self) -> Option<&str> { + self.portuguese.as_deref() + } + + #[must_use] + pub fn russian(&self) -> Option<&str> { + self.russian.as_deref() + } + + #[must_use] + pub fn german(&self) -> Option<&str> { + self.german.as_deref() + } + + #[must_use] + pub fn korean(&self) -> Option<&str> { + self.korean.as_deref() + } + + #[must_use] + pub fn japanese(&self) -> Option<&str> { + self.japanese.as_deref() + } + + #[must_use] + pub fn chinese(&self) -> Option<&str> { + self.chinese.as_deref() + } + + #[must_use] + pub fn chinesesimp(&self) -> Option<&str> { + self.chinesesimp.as_deref() + } + + #[must_use] + pub fn turkish(&self) -> Option<&str> { + self.turkish.as_deref() + } + + #[must_use] + pub fn swedish(&self) -> Option<&str> { + self.swedish.as_deref() + } + + #[must_use] + pub fn slovak(&self) -> Option<&str> { + self.slovak.as_deref() + } + + #[must_use] + pub fn serbocroatian(&self) -> Option<&str> { + self.serbocroatian.as_deref() + } + + #[must_use] + pub fn norwegian(&self) -> Option<&str> { + self.norwegian.as_deref() + } + + #[must_use] + pub fn icelandic(&self) -> Option<&str> { + self.icelandic.as_deref() + } + + #[must_use] + pub fn hungarian(&self) -> Option<&str> { + self.hungarian.as_deref() + } + + #[must_use] + pub fn greek(&self) -> Option<&str> { + self.greek.as_deref() + } + + #[must_use] + pub fn finnish(&self) -> Option<&str> { + self.finnish.as_deref() + } + + #[must_use] + pub fn dutch(&self) -> Option<&str> { + self.dutch.as_deref() + } +} diff --git a/libs/stringtable/src/lib.rs b/libs/stringtable/src/lib.rs new file mode 100644 index 00000000..4dfb5fcb --- /dev/null +++ b/libs/stringtable/src/lib.rs @@ -0,0 +1,59 @@ +use quick_xml::se::Serializer; +use serde::{Deserialize, Serialize}; + +mod key; +mod package; +mod totals; + +pub use key::Key; +pub use package::Package; +pub use totals::Totals; + +#[derive(Debug, Deserialize, Serialize)] +pub struct Project { + #[serde(rename = "@name")] + name: String, + #[serde(rename = "Package")] + packages: Vec, +} + +impl Project { + #[must_use] + pub fn name(&self) -> &str { + &self.name + } + + #[must_use] + pub fn packages(&self) -> &[Package] { + &self.packages + } + + pub fn sort(&mut self) { + self.packages.sort_by(|a, b| a.name().cmp(b.name())); + for package in &mut self.packages { + package.sort(); + } + } + + /// Read a Project from a reader + /// + /// # Errors + /// [`quick_xml::de::DeError`] if the reader is not a valid stringtable + pub fn from_reader(reader: R) -> Result { + quick_xml::de::from_reader(reader) + } + + /// Write a Project to a writer + /// + /// # Errors + /// [`quick_xml::ser::Error`] if the writer fails to write + pub fn to_writer( + &self, + writer: &mut W, + ) -> Result<(), quick_xml::de::DeError> { + let mut ser = Serializer::new(writer); + ser.indent(' ', 4); + + self.serialize(ser) + } +} diff --git a/libs/stringtable/src/package.rs b/libs/stringtable/src/package.rs new file mode 100644 index 00000000..6c7880a9 --- /dev/null +++ b/libs/stringtable/src/package.rs @@ -0,0 +1,123 @@ +use serde::{Deserialize, Serialize}; + +use crate::{Key, Totals}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct Package { + #[serde(rename = "@name")] + name: String, + #[serde(rename = "Key")] + #[serde(default)] + keys: Vec, + #[serde(rename = "Container")] + #[serde(default)] + containers: Vec, +} + +impl Package { + #[must_use] + pub fn name(&self) -> &str { + &self.name + } + + #[must_use] + pub fn keys(&self) -> &[Key] { + &self.keys + } + + #[must_use] + pub fn containers(&self) -> &[Self] { + &self.containers + } + + pub fn sort(&mut self) { + self.keys.sort_by(|a, b| a.id().cmp(b.id())); + for container in &mut self.containers { + container.sort(); + } + } + + #[must_use] + pub fn totals(&self) -> Totals { + let mut totals = Totals::default(); + for key in &self.keys { + totals.inc(); + if key.original().is_some() { + totals.inc_original(); + } + if key.english().is_some() { + totals.inc_english(); + } + if key.czech().is_some() { + totals.inc_czech(); + } + if key.french().is_some() { + totals.inc_french(); + } + if key.spanish().is_some() { + totals.inc_spanish(); + } + if key.italian().is_some() { + totals.inc_italian(); + } + if key.polish().is_some() { + totals.inc_polish(); + } + if key.portuguese().is_some() { + totals.inc_portuguese(); + } + if key.russian().is_some() { + totals.inc_russian(); + } + if key.german().is_some() { + totals.inc_german(); + } + if key.korean().is_some() { + totals.inc_korean(); + } + if key.japanese().is_some() { + totals.inc_japanese(); + } + if key.chinese().is_some() { + totals.inc_chinese(); + } + if key.chinesesimp().is_some() { + totals.inc_chinesesimp(); + } + if key.turkish().is_some() { + totals.inc_turkish(); + } + if key.swedish().is_some() { + totals.inc_swedish(); + } + if key.slovak().is_some() { + totals.inc_slovak(); + } + if key.serbocroatian().is_some() { + totals.inc_serbocroatian(); + } + if key.norwegian().is_some() { + totals.inc_norwegian(); + } + if key.icelandic().is_some() { + totals.inc_icelandic(); + } + if key.hungarian().is_some() { + totals.inc_hungarian(); + } + if key.greek().is_some() { + totals.inc_greek(); + } + if key.finnish().is_some() { + totals.inc_finnish(); + } + if key.dutch().is_some() { + totals.inc_dutch(); + } + } + for container in &self.containers { + totals.merge(&container.totals()); + } + totals + } +} diff --git a/libs/stringtable/src/totals.rs b/libs/stringtable/src/totals.rs new file mode 100644 index 00000000..492030f1 --- /dev/null +++ b/libs/stringtable/src/totals.rs @@ -0,0 +1,107 @@ +#[derive(Debug, Default, Clone, Copy)] +pub struct Totals { + total: u32, + original: u32, + english: u32, + czech: u32, + french: u32, + spanish: u32, + italian: u32, + polish: u32, + portuguese: u32, + russian: u32, + german: u32, + korean: u32, + japanese: u32, + chinese: u32, + chinesesimp: u32, + turkish: u32, + swedish: u32, + slovak: u32, + serbocroatian: u32, + norwegian: u32, + icelandic: u32, + hungarian: u32, + greek: u32, + finnish: u32, + dutch: u32, +} + +macro_rules! field { + ($field:ident) => { + paste::paste! { + pub fn [](&mut self) { + self.$field += 1; + } + + #[must_use] + pub const fn $field(&self) -> u32 { + self.$field + } + } + }; +} + +impl Totals { + pub fn inc(&mut self) { + self.total += 1; + } + + #[must_use] + pub const fn total(&self) -> u32 { + self.total + } + + pub fn merge(&mut self, other: &Self) { + self.total += other.total; + self.original += other.original; + self.english += other.english; + self.czech += other.czech; + self.french += other.french; + self.spanish += other.spanish; + self.italian += other.italian; + self.polish += other.polish; + self.portuguese += other.portuguese; + self.russian += other.russian; + self.german += other.german; + self.korean += other.korean; + self.japanese += other.japanese; + self.chinese += other.chinese; + self.chinesesimp += other.chinesesimp; + self.turkish += other.turkish; + self.swedish += other.swedish; + self.slovak += other.slovak; + self.serbocroatian += other.serbocroatian; + self.norwegian += other.norwegian; + self.icelandic += other.icelandic; + self.hungarian += other.hungarian; + self.greek += other.greek; + self.finnish += other.finnish; + self.dutch += other.dutch; + } + + field!(original); + field!(english); + field!(czech); + field!(french); + field!(spanish); + field!(italian); + field!(polish); + field!(portuguese); + field!(russian); + field!(german); + field!(korean); + field!(japanese); + field!(chinese); + field!(chinesesimp); + field!(turkish); + field!(swedish); + field!(slovak); + field!(serbocroatian); + field!(norwegian); + field!(icelandic); + field!(hungarian); + field!(greek); + field!(finnish); + field!(dutch); +} diff --git a/libs/stringtable/tests/ace_arsenal.rs b/libs/stringtable/tests/ace_arsenal.rs new file mode 100644 index 00000000..c93d512c --- /dev/null +++ b/libs/stringtable/tests/ace_arsenal.rs @@ -0,0 +1,18 @@ +#![allow(clippy::unwrap_used)] + +use hemtt_stringtable::Project; + +#[test] +fn sort() { + let mut stringtable: Project = quick_xml::de::from_str( + std::fs::read_to_string("tests/ace_arsenal.xml") + .unwrap() + .as_str(), + ) + .unwrap(); + insta::assert_debug_snapshot!(stringtable); + + stringtable.sort(); + + insta::assert_debug_snapshot!(stringtable); +} diff --git a/libs/stringtable/tests/ace_arsenal.xml b/libs/stringtable/tests/ace_arsenal.xml new file mode 100644 index 00000000..38f41ef4 --- /dev/null +++ b/libs/stringtable/tests/ace_arsenal.xml @@ -0,0 +1,1740 @@ + + + + + Hide + Ocultar + Cacher + Verstecken + Ukryj + 隠す + Nascondi + 숨김 + 隱藏 + 隐藏 + Спрятать + Ocultar + Skrýt + Gizle + + + Hide interface + Ocultar interfaz + Masque l'interface + Oberfläche verstecken + Ukryj interfejs + インタフェースを隠す + Nascondi interfaccia + 인터페이스 숨기기 + 隱藏介面 + 隐藏界面 + Скрыть интерфейс + Oculta a Interface + Skrýt rozhraní + Arayüzü gizle + + + Loadouts + Equipamiento + Sets d'équipement + Ausrüstungen + Zestawy wyposażenia + 装備 + Equipaggiamenti + 로드아웃 + 裝備 + 负载 + Комплекты + Loadouts + Sady vybavení + Kıyafetler + + + Export + Exportar + Exporter + Exportieren + Eksportuj + エクスポート + Esporta + 내보내기 + 匯出 + 导出 + Экспорт + Exportar + Export + Dışa Aktar + + + Import + Importar + Importer + Importieren + Importuj + インポート + Importa + 가져오기 + 匯入 + 导入 + Импорт + Importar + Import + Içe Aktar + + + Close + Cerrar + Fermer + Schließen + Zamknij + 閉じる + Chiudi + 닫기 + 關閉 + 关闭 + Закрыть + Fechar + Zavřít + Kapat + + + No virtual items available + Ningún objeto virtual disponible + Aucun objet virtuel disponible. + Kein virtuelles Objekt verfügbar + Brak dostępnych przedmiotów wirtualnych + 利用可能な仮想アイテムはありません + Nessun oggetto virtuale disponibile + 가상장비 사용 불가 + 沒有可用的虛擬物品 + 没有可用的虚拟物品 + Виртуальный предмет недоступен + Nenhum item virtual disponível + Není dostupný žádný virtuální předmět + Kullanılabilir sanal öğe yok + + + Save + Guardar + Enregistrer + Speichern + Zapisz + 保存 + Salva + 저장 + 保存 + 保存 + Сохранить + Salvar + Uložit + Kaydet + + + Save the current loadout + Guardar el equipamiento actual + Enregistre l'équipement actuel. + Ausgewählte Ausrüstung speichern + Zapisz obecny zestaw + 現在の装備を保存します + Salva l'equipaggiamento corrente + 현재 로드아웃 저장 + 保存當前的裝備 + 保存当前的负载 + Сохранить текущий комплект экипировки + Salva o loadout atual + Uložit současnou sadu vybavení + Geçerli kıyafetleri kaydet + + + [Shift+Click to save to mission defaults] + [Shift+Click para guardar equipamiento predefinido + [Umschalt+Linksklick um als Standard-Missionsausrüstung zu speichern] + [Shift + クリックでミッションの標準装備として保存します] + Shift + Klik aby zapisac jako domyślne dla misji + [Shift+Клик, чтобы сохранить в настройках по умолчанию] + [Shift+Clique para salvar nos padrões da missão] + [Shift+Clic pour enregistrer en tant qu'équipement prédéfini.] + [Shift+左鍵來保存至任務預設] + [Shift+左鍵 以保存至任务默认值] + [Shift+Click per salvare come equipaggiamento predefinito della missione] + [Shift+Klik pro uložení jako standardního vybavení pro misi] + [Shift+Click varsayılan kıyafetlere kaydet] + [쉬프트+클릭 하여 임무 기본으로 설정] + + + Rename the selected loadout + Renombrar el equipamiento seleccionado + Renomme le set d'équipement sélectionné. + Ausgewählte Ausrüstung umbenennen + Zmień nazwę wybranego zestawu + 選択中の装備を改名します + Rinomina l'equipaggiamento selezionato + 선택한 로드아웃의 이름 바꾸기 + 重新命名當前選擇的裝備 + 重命名当前选择的负载 + Переименовать выбранный комплект экипировки + Renomeia o loadout selecionado + Přejmenovat vybranou sadu vybavení + Seçili kıyafetleri yeniden adlandır + + + Load + Cargar + Charger + Laden + Wczytaj + 読み込む + Carica + 불러오기 + 載入 + 载入 + Загрузить + Carregar + Nahrát + Yükle + + + Load the selected loadout + Cargar el equipamiento seleccionado + Charge le set d'équipement sélectionné. + Ausgewählte Ausrüstung laden + Wczytaj wybrany zestaw + 選択中の装備を読み込みます + Carica l'equipaggiamento selezionato + 선택한 로드아웃 불러오기 + 載入當前選擇的裝備 + 载入当前选择的负载 + Загрузить выбранный комплект экипировки + Carrega o loadout selecionado + Nahrát vybranou sadu vybavení + Seçili kıyafetleri yükle + + + Delete + Eliminar + Supprimer + Entfernen + Skasuj + 削除 + Elimina + 삭제 + 刪除 + 删除 + Удалить + Apagar + Smazat + Sil + + + Delete the selected loadout + Eliminar el equipamiento seleccionado + Supprime le set d'équipement sélectionné. + Ausgewählte Ausrüstung entfernen + Skasuj wybrany zestaw + 選択中の装備を削除します + Elimina l'equipaggiamento selezionato + 선택한 로드아웃 삭제하기 + 刪除當前選擇的裝備 + 删除当前选择的负载 + Удалить выбранный комплект экипировки + Apaga o loadout selecionado + Smazat vybranou sadu vybavení + Seçili kıyafetleri sil + + + My loadouts + Mis equipamientos + Mes équipements + Meine Ausrüstungen + Moje zestawy + 自分の装備 + I miei equipaggiamenti + 내 로드아웃 + 我的裝備 + 我的负载 + Мои комплекты + Meus loadouts + Moje sady vybavení + Benim kıyafetlerim + + + Loadouts saved in your profile + Equipamientos guardados en el perfil + Sets d'équipement enregistrés dans votre profil. + Ausrüstungen, die in deinem Profil gespeichert sind + Zestawy zapisane w Twoim profilu + プロフィールに保存された装備です + Gli equipaggiamenti salvati nel tuo profilo + 프로필에 저장된 로드아웃 + 裝備已保存到你的設定檔中 + 负载已保存到你的档案中 + Комплекты экипировки, сохраненные в вашем профиле + Loadouts salvos em seu perfil + Sadz vybavení uložené ve vašem profilu + Profiline kaydedilmiş kıyafetlerin + + + Default loadouts + Equipamientos por defecto + Équipements prédéfinis + Standard-Ausrüstungen + Domyślne zestawy + 標準装備 + Equipaggiamenti predefiniti + 기본 로드아웃 + 預設裝備 + 默认负载 + По умолчанию + Loadouts padrões + Standardní sady vybavení + Varsayılan Kıyafetler + + + Loadouts made available by the mission maker + Equipamientos disponibles del editor de la misión + Sets d'équipement mis à disposition par le créateur de mission. + Ausrüstungen, die durch den Missionsersteller zur Verfügung gestellt worden sind + Zestawy udostępnione przez twórcę misji + ミッション著者によって作成された装備です + Equipaggiamenti resi disponibili dal creatore della missione + 미션메이커가 허용한 로드아웃 + 任務作者提供的預設裝備 + 任务作者提供的负载 + Комплекты экипировки, предоставляемые создателем миссии + Loadouts definidos pelo criador da missão + Sady vybavení od autora mise + Görevi hazırlayan kişi tarafından kullanıma sunulan teçhizatlar + + + Public loadouts + Equipamientos públicos + Équipements publics + Veröffentlichte Ausrüstungen + Publiczne zestawy + 公開装備 + Equipaggiamenti pubblici + 공용 로드아웃 + 公用裝備 + 公用负载 + Публичные комплекты + Loadouts públicos + Veřejné sady vybavení + Herkese Açık Kıyafetler + + + Loadouts shared by you and other players + Equipamientos compartidos por ti y otros jugadores + Sets d'équipement partagés par vous-même ou par d'autres joueurs. + Ausrüstungen, die von dir und anderen Spielern geteilt wurden + Zestawy udostępnione przez Ciebie i innych graczy + 自分か他人によって共有された装備です + Equipaggiamenti condivisi da te e da altri giocatori + 플레이어들이 공유하는 로드아웃 + 由你與其他玩家分享的裝備配置 + 你和其他玩家分享的负载配置 + Комплекты экипировки, опубликованные вами и другими игроками + Loadouts compartilhados por você ou outros jogadores + Sady vybavení sdílené vámi a ostatními hráči + Senin veya diğer kişiler tarafından paylaşılan kıyafetler + + + Sort by weight + Ordenar por peso + Trier par poids + Nach Gewicht sortieren + Sortuj wg wagi + 重量順に並び替え + Ordina per peso + 무게 순서로 정렬 + 以重量排序 + 以重量排序 + Сортировка по весу + Ordenar por peso + Seřadit podle váhy + Kiloya göre sırala + + + Sort by amount + Ordenar por cantidad + Trier par quantité + Nach Menge sortieren + Sortuj wg ilości + 数量順に並び替え + Ordina per quantità + 갯수 순서로 정렬 + 以數量排序 + 以数量排序 + Сортировка по количеству + Ordenar por quantidade + Seřadit podle množství + Miktara göre sırala + + + Sort by load + Nach Tragelast sortieren + Trier par capacité de chargement + Ordina per capacità di carico + 容量順に並び替え + Ordenar por capacidad + Сортировка по вместимости + Sortuj po rozmiarze + 공간 순서로 정렬 + 以容量排序 + Ordenar por capacidade + + + Sort by accuracy + Nach Genauigkeit sortieren + Trier par précision + Ordina per precisione + 精度順に並び替え + Isabet doğruluğuna göre sırala + Ordenar por precisión + Сортировка по точности + Sortuj po celności + 정확도 순서로 정렬 + 以精度排序 + Ordenar por precisão + + + Sort by rate of fire + Nach Schussrate sortieren + Trier par cadence de tir + Ordina per cadenza di tiro + 連射速度順に並び替え + Atış hızına göre sırala + Ordenar por cadencia de tiro + Сортировка по темпу стрельбы + Sortuj po szybkostrzelności + 발사속도 순서로 정렬 + 以射速排序 + Ordenar por cadência + + + Sort by magnification + Nach Vergrößerung sortieren + Trier par grossissement + Ordina per ingrandimento + 倍率順に並び替え + Ordenar por magnificación + Сортировка по кратности приближения + Sortuj po przybliżeniu + 배율 순서로 정렬 + 以放大倍数排序 + Ordenar por magnificação + + + Sort by ammo count + Nach Munitionszahl sortieren + Trier par nombre de munitions + Ordina per numero di colpi + 装弾数順に並び替え + Mermi sayısına göre sırala + Ordenar por cantidad de munición + Сортировка по количеству боеприпасов + Sortuj po ilości amunicji + 총알 갯수 순서롤 정렬 + 以弹量排序 + Ordenar por quantidade de munição + + + Sort by ballistic protection + Trier par protection balistique + Ordina per protezione balistica + 防弾性能順に並び替え + Ordenar por protección balística + Сортировка по баллистической защите + Sortuj po ochronie balistycznej + Nach ballistischem Schutz sortieren + 방탄 성능 순서로 정렬 + 以防弹性能排序 + Ordenar por proteção balística + + + Sort by explosive protection + Trier par résistance aux explosifs + Ordina per protezione esplosiva + 防爆性能順に並び替え + Ordenar por protección de explosivos + Сортировка по защите от взрывов + Sortuj po ochronie przeciw wybuchom + Nach Explosionsschutz sortieren + 방폭 성능 순서로 정렬 + 以防爆性能排序 + Ordenar por proteção a explosivos + + + Share or stop sharing the selected loadout + Compartir o dejar de compartir el equipamiento seleccionado + Partager ou cesser de partager le set d'équipement sélectionné. + Ausgewählte Ausrüstung teilen oder nicht mehr teilen + Udostępnij lub przestań udostępniać wybrany zestaw + 選択した装備の共有設定 + Condividi o smetti di condividere l'equipaggiamento selezionato + 선택한 로드아웃 공유 혹은 공유 중지 + 開始/停止分享當前選擇的裝備 + 开始/停止分享当前选择的负载 + Открыть или закрыть общий доступ к комплекту экипировки + Compartilhar ou parar de compartilhar o loadout selecionado + Sdílet nebo přestat sdílet vybranou sadu vybavení + Seçili kıyafeti paylaş ya da paylaşmayı durdur. + + + Private + Privado + Privé + Privat + Prywatny + 非公開 + Privato + 개인 + 私用 + 私用 + Приватный + Privado + Soukromé + Özel + + + Public + Público + Public + Öffentlich + Publiczny + 公開 + Pubblico + 공용 + 公用 + 公用 + Публичный + Público + Veřejné + Herkese Açık + + + The default loadouts list is empty! + La lista de equipamientos por defecto está vacía! + La liste des équipements prédéfinis est vide ! + Die Standard-Ausrüstungen-Liste ist leer! + Lista domyślnych zestawów jest pusta! + 標準装備の一覧は空です! + La lista degli equipaggiamenti predefiniti è vuota! + 기본 로드아웃 목록이 비어있습니다! + 沒有預設的裝備清單! + 没有默认负载清单! + Список комплекта экипировки пуст! + A lista de loadouts padrões está vazia! + Seznam standardních sad vybavení je prázdný! + Varsayılan kıyafetler listesi boş ! + + + Default loadouts list exported to clipboard + Lista de equipamientos por defecto exportada al portapapeles + Liste des équipements prédéfinis exportée dans le presse-papier. + Standard-Ausrüstungen-Liste in die Zwischenablage exportiert + Lista domyślnych zestawów została eksportowana do schowka + 標準装備の一覧をクリップボードへエクスポートしました + La lista degli equipaggiamenti predefiniti è stata esportata negli appunti + 클립보드에 기본 로드아웃 목록 내보내기 + 預設的裝備清單已匯出到剪貼簿中 + 默认负载清单已导出到剪贴板 + Список комплекта экипировки по умолчанию экспортирован в буфер + A lista de loadouts padrões foi exportada pra área de transferência + Seznam standardních sad vybavení byl exportován do schránky + Varsayılan kıyafetler listesi panoya dışa aktar + + + Current loadout exported to clipboard + Equipamiento actual exportado al portapapeles + Équipement actuel exporté dans le presse-papier. + Derzeitige Ausrüstung in die Zwischenablage exportiert + Obecny zestaw został eksportowany do schowka + 現在の装備をクリップボードへ出力しました + Equipaggiamento corrente esportato negli appunti + 현재 로드아웃을 클립보드로 내보냈습니다. + 當前的裝備已匯出到剪貼簿中 + 当前负载已导出到剪贴板 + Текущий список комплекта экипировки экспортирован в буфер + Loadout atual foi exportado pra área de transferência + Současná sada vybavení byla exportována do schránky + Geçerli kıyafeti panoya dışa aktar + + + Wrong format provided + Formato incorrecto proporcionado + Mauvais format fourni. + Falsches Format verwendet + Podano zły format + 間違ったフォーマットが入力されました + Formato fornito sbagliato + 잘못된 형식 입력됨 + 提供的格式錯誤 + 提供的格式错误 + Неверный формат импорта + Format incorreto fornecido + Byl dodán špatný formát + Yanlış format + + + Default loadouts list imported from clipboard + Lista de equipamientos importada desde el portapapeles + Liste des équipements prédéfinis importée depuis le presse-papier. + Standard-Ausrüstungen-Liste aus der Zwischenablage importiert + Lista domyślnych zestawów została importowana ze schowka + 標準装備の一覧をクリップボードからインポートしました + La lista degli equipaggiamenti predefiniti è stata importata dagli appunti + 클립보드에서 기본 로드아웃 가져오기 + 預設的裝備清單已從剪貼簿中匯入 + 默认负载已从剪贴板导入 + Список комплекта экипировки по умолчанию импортирован из буфера + A lista de loadouts padrões foi importada da área de transferência + Seznam standardních sad vybavení byl importován ze schránky + Varsayılan kıyafetler listesini içe aktar + + + Loadout imported from clipboard + Equipamiento importado del portapapeles + Set d'équipement importé depuis le presse-papier. + Ausrüstung aus der Zwischenablage importiert + Zestaw został importowany ze schowka + 装備をクリップボードからインポートしました + Equipaggiamento importato dagli appunti + 클립보드에서 로드아웃을 가져왔습니다. + 裝備已從剪貼簿中匯入 + 负载已从剪贴板中导入 + Список комплекта экипировки импортирован из буфера + Loadout importado da área de transferência + Sada vybavení byla importována ze schránky + Kıyafetleri içe aktar + + + The following loadout was deleted: + El siguiente equipamiento ha sido eliminado: + Le set d'équipement suivant a été supprimé : + Folgende Ausrüstung wurde entfernt: + Następujący zestaw został skasowany: + 装備を削除しました: + Il seguente equipaggiamento è stato eliminato: + 다음 로드아웃이 삭제됨 : + 以下的裝備已被刪除: + 以下的负载已被删除: + Удален комплект экипировки: + O seguinte loadout foi apagado: + Tato sada vybavení byla smazána: + Bu kıyafet silindi: + + + The following loadout is not public anymore: + El siguiente equipamiento ha dejado de ser público: + Le set d'équipement suivant n'est plus public : + Folgende Ausrüstung ist nicht mehr öffentlich: + Następujący zestaw nie jest już publiczny: + 装備を非公開にしました: + Il seguente equipaggiamento non è più pubblico: + 다음 로드아웃이 더이상 공용이 아님: + 以下的裝備已不再被分享: + 以下的负载已不再被分享: + Этот комплект экипировки больше не публичный: + O seguinte loadout não é mais público: + Tato sada vybavení již není veřejná: + Bu kıyafet artık herkese açık değil : + + + The name field is empty! + El campo de nombre está vacío! + Le champ "nom" est vide ! + Das Feld "Name" ist leer! + Pole nazwy jest puste! + 名前が空欄です! + Il campo del nome è vuoto! + 이름칸이 비었습니다! + 名稱欄位為空! + 名称栏位为空! + Поле имени пустое! + O nome não pode estar vazio! + Pole "Jméno" je prázdné! + Isim alanı boş! + + + You are the author of this loadout + Tú eres el autor de este equipamiento + Vous êtes l'auteur de ce set d'équipement. + Du bist der Ersteller dieser Ausrüstung + Jesteś autorem tego zestawu + あなたはこの装備の作者です + Sei l'autore di questo equipaggiamento + 이 로드아웃의 제작자입니다. + 你是這個裝備的作者 + 你是这个负载的作者 + Вы автор этого комплекта экипировки + Você é o autor desse loadout + Jste autorem této sady vybavení + Bu kıyafetin sahibi sensin + + + A loadout of yours with the same name is public + Un equipamiento tuyo con el mismo nombre ya es público + Un de vos sets d'équipement ayant le même nom est public. + Eine deiner Ausrüstungen mit dem gleichen Namen ist öffentlich + Jeden z Twoich zestawów nazwany tak samo jest już publiczny + あなたのものと同じ名前の装備が既に公開されています + Un tuo equipaggiamento con lo stesso nome è pubblico + 같은 이름의 로드아웃이 공용에 있습니다. + 已有相同名稱的裝備在公用分享區 + 已有相同名称的负载在公用分享区 + Ваш комплект экипировки с таким же именем является публичным + Um loadout seu com o mesmo nome é público + Vaše sada vybavení se stejným jménem je veřejná + Bu kıyafetler zaten aynı isim de paylaşılmış. + + + The following loadout was saved: + El siguiente equipamiento ha sido guardado: + Le set d'équipement suivant a été enregistré : + Folgende Ausrüstung wurde gespeichert: + Następujący zestaw został zapisany: + 装備を保存しました: + Il seguente equipaggiamento è stato salvato: + 다음 로드아웃이 저장됨: + 以下的裝備已被保存: + 以下的负载已被保存: + Сохранен комплект экипировки: + O seguinte loadout foi salvo: + Tato sada vybavení byla uložena: + Bu kıyafet kaydedildi: + + + The following loadout was loaded: + El siguiente equipamiento ha sido cargado: + Le set d'équipement suivant a été chargé : + Folgene Ausrüstung wurde geladen: + Następujący zestaw został wczytany: + 装備を読み込みました: + Il seguente equipaggiamento è stato caricato: + 다음 로드아웃을 불러옴: + 以下的裝備已被載入: + 以下的负载已被载入: + Загружен комплект экипировки: + O seguinte loadout foi carregado: + Tato sada vybavení byla načtena: + Bu kıyafet yüklendi: + + + A loadout with the same name already exist! + Ya existe un equipamiento con el mismo nombre! + Un set d'équipement ayant le même nom existe déjà ! + Eine Ausrüstung mit dem gleichen Namen existiert bereits! + Zestaw z tą nazwą już istnieje! + 既に同じ名前の装備が存在しています! + Un equipaggiamento con lo stesso nome è gia esistente! + 같은 이름의 로드아웃이 이미 존재합니다! + 已有相同名稱的裝備! + 已有相同名称的负载! + Комплект с таким именем уже существует! + Um loadout com o mesmo nome já existe! + Již existuje sada vybavení se stejným jménem! + Aynı isim de başka kıyafetler var! + + + was renamed to + ha sido renombrado a + a été renommé en + wurde umbenannt in + zmienił nazwę na + を次の名前に変更しました: + È stato rinominato in + 이름이 다음과 같이 변경됨: + 已被改名為 + 已被改名为 + был переименован в + foi renameado para + bylo přejmenováno na + değişti, yeni isim + + + Invert camera controls + Invertir controles de cámara + Inverser les contrôles de la caméra + Kamerasteuerung invertieren + Odwróć sterowanie kamerą + カメラ操作を反転 + Inverti comandi camera + 카메라 조종 반전 + 反轉攝影機控制 + 反转摄影机控制 + Инвертировать управление камерой + Inverter controles da câmera + Obrátit ovládání kamery + Kamera kontrollerini ters çevir + + + Enable mod icons + Habilitar iconos de mods + Afficher les icônes de mod + Aktiviert Mod-Icons + Włącz ikony modów + MOD アイコンを表示 + Abilita icone mod + 모드 아이콘 허가 + 啟用模組圖示 + 启用模组图示 + Вкл. иконки модов + Ativar ícones de mods + Zapnout ikony modů + Mod simgelerini etkinleştir + + + Panel font height + Tamaño de fuente del panel + Taille de police des panneaux + Schrifthöhe für die linke und rechte Liste + Wysokość czcionki + パネルのフォントの高さ + Altezza carattere del pannello + 패널 폰트 높이 + 面板字體高度 + 面板字体高度 + Размер шрифта панели + Altura da fonte do painel + Výška fontu panelů + Panel yazı tipi büyüklüğü + + + Allow default loadouts + Permitir equipamientos por defecto + Autoriser les équipements prédéfinis + Erlaubt die Benutzung der Standardausrüstungen + Zezwól na użycie domyślnych zestawów + 標準装備を許可 + Consenti equipaggiamenti predefiniti + 기본 로드아웃 허용 + 允許預設裝備 + 允许默认负载 + Разрешить комплекты по умолчанию + Permitir loadouts padrões + Povolit standardní sady vybavení + Varsayılan kıyafetlere izin ver + + + Allow loadout sharing + Permitir compartir equipamientos + Autoriser le partage des sets d'équipement + Erlaubt das Teilen von Ausrüstungen + Zezwól na udostępnianie zestawów + 装備共有を許可 + Consenti condivisione equipaggiamenti + 로드아웃 공유 허용 + 允許分享裝備 + 允许分享负载 + Разрешить публикацию комплектов + Permitir compartilhar loadouts + Povolit sdílení sad vybavení + Kıyafet paylaşmaya izin ver + + + Log missing / unavailable items + Registrar los objetos no encontrados o no disponibles + Consigner les objets manquants ou indisponibles + Aktiviert die Aufzeichnung fehlender Gegenstände in der RPT + Rejestruj brakujące / niedostępne przedmioty + 欠落 / 利用不可アイテムを記録 + Log mancante / oggetto non disponibile + 누락 된 항목 / 사용 할 수 없는 항목 기록 + 記錄遺失/無法使用的項目 + 记录遗失/无法使用的项目 + Вести журнал недоступных предметов + Registrar em log itens indisponíveis/não encontrados + Zalogovat chybějící/nedostupné předměty + Kayıp / mevcut olmayan öğeleri günlüğe kaydet + + + Primary magazine + Cargador principal + Chargeur principal + Główny magazynek + プライマリ弾倉 + Caricatore primario + 주무기 탄약 + Primärmagazin + 主要武器彈匣 + 主武器弹匣 + Основной магазин + Carregador Primário + Hlavní zásobník + Birinci Cephane + + + Secondary magazine + Cargador secundario + Chargeur secondaire + Dodatkowy magazynek + セカンダリ弾倉 + Caricatore secondario + 보조무기 탄약 + Sekundärmagazin + 次要武器彈匣 + 副武器弹匣 + Вторичный магазин + Carregador Secundário + Vedlejší zásobník + Ikinci Cephane + + + ACE Arsenal + ACE Arsenal + ACE Arsenal + ACE-Arsenal + ACE Arsenał + ACE 武器庫 + ACE Arsenale + ACE 무기고 + ACE虛擬軍火庫 + ACE 虚拟军火库 + ACE Арсенал + ACE Arsenal + ACE Arzenál + ACE Arsenal + + + Loadouts + Equipamiento + Sets d'équipement + Ausrüstungen + Zestawy wyposażenia + 装備 + Equipaggiamenti + 로드아웃 + 裝備 + 负载 + Комплекты + Loadouts + Sady vybavení + Kıyafetler + + + Allow the use of the default loadouts tab + Permitir el uso de la pestaña de equipamientos por defecto + Active l'onglet "Équipements prédéfinis". + Zezwól na użycie zakładki domyślnych zestawów + 標準装備タブの使用を許可します + Consenti l'uso della sezione per gli equipaggiamenti predefiniti + 기본 로드아웃 탭 사용 허가 + Erlaube die Nutzung des Standardausrüstungsreiters + 允許使用預設的裝備 + 允许使用默认的负载 + Разрешить использование вкладки комплектов экипировки по умолчанию + Permite o uso da aba de loadouts padrões + Povolit používání záložky standardních sad vybavení + Varsayılan Kıyafetler sekmesinin kullanımına izin ver + + + Show / hide mod icons for the left panel + Mostrar / ocultar iconos de mods en el panel izquierdo + Affiche/masque les icônes de mod du panneau gauche. + Pokaż / ukryj ikony modów w lewym panelu + 左パネルにあるMODアイコンを表示/非表示します + Mostra / nascondi le icone delle mod dal pannello sinistro + 왼쪽 패널의 모드 아이콘 표시 / 숨기기 + Zeigt/Versteckt Mod-Symbole in der linken Leiste + 在左面板中顯示/隱藏模組圖示 + 在左面板中显示/隐藏模组图示 + Показать / скрыть значки модов в левой панели + Mostra / Esconde os ícones de mods no painel esquerdo + Ukázat/Skrýt ikony modů v levém panelu + Sol panel de mod ikonlarını göster/gizle + + + Change the font height for text in the left / right panels + Cambiar el tamaño de fuente para el texto de los paneles izquierdo y derecho + Change la taille de police des panneaux latéraux. + Zmień wysokość czcionki dla tekstu lewego i prawego panelu + 左右パネル内の文字フォントの高さを変更します。 + Cambia l'altezza del font per il testo sul pannello sinistro / destro + 왼쪽 / 오른쪽 패널 텍스트의 글꼴 높이 변경 + Ändert die Schriftgröße für die linke/rechte Leiste + 變更左/右面板中的字體高度 + 变更左/右面板中的字体高度 + Изменить размер шрифта для текста в левой / правой панелях + Muda o tamanho da fonte para os textos nos painéis esquerdo e direito + Změnit výšku fontu pro text v levém/pravém panelu + Sağ ve sol panel de ki yazıların boyutunu değiştir. + + + Log missing / unavailable items in the RPT + Registrar elementos no encontrados o no disponibles en el RPT + Consigne les objets manquants ou indisponibles dans le RPT. + Rejestruj brakujące / niedostępne przedmioty do pliku RPT + PRTに欠落/利用不可アイテムを記録します + Log mancante / oggetto non disponibile nell' RPT + RPT에 누락 된 항목 / 사용할 수없는 항목 기록 + Fehlende Gegenstände werden in der RPT aufgezeichnet + 記錄遺失/無法使用的項目到RPT檔案中 + 记录遗失/无法使用的项目到 RPT + Вести журнал отсутствующих / недоступных предметов в RPT + Registrar em log itens indisponíveis no RPT + Zalogovat chybějící/nedostupné předměty do RPT logu + Kayıp / mevcut olmayan öğeleri RPT'nin içine kaydet. + + + Unable to open ACE arsenal + No es posible abrir el arsenal de ACE + Impossible d'ouvrir l'arsenal ACE. + Kann ACE-Arsenal nicht anzeigen + Impossibile aprire l'arsenale ACE + ACE 武器庫を開けません + ACE 아스날을 열 수 없음 + 無法開啟ACE虛擬軍火庫 + 无法开启 ACE 虚拟军火库 + Nie można otworzyć arsenału ACE + Невозможно открыть ACE Арсенал + Não foi possível abrir o ACE Arsenal + Nepodařilo se otevřít ACE Arzenál + ACE Arsenal açılamıyor + + + Import BI VA loadouts to ACE Arsenal + Importar equipamientos de BI Arsenal hacia el arsenal de ACE + Importer les sets BI VA dans l'arsenal ACE + Importiert die BI-VA-Ausrüstungen in das ACE-Arsenal + BI 武器庫の装備を ACE 武器庫へインポート + 바닐라 로드아웃을 ACE 아스날로 가져오기 + 匯入BI原廠虛擬軍火庫的裝備到ACE虛擬軍火庫中 + 导入 BI 原版虚拟军火库的负载到 ACE 虚拟军火库中 + Importa l'arsenale virtuale BI nell'arsenale ACE + Importuj zestawy wyposażenia z wirtualnego arsenału BI do arsenału ACE + Импорт комплектов из Арсенала BI в Арсенал ACE + Importar loadouts do BIS Arsenal para o ACE Arsenal + Importovat sady vybavení z BI VA do ACE Arzenálu + + + No player unit available! Place a unit and mark it as "Player". + Ninguna unidad de jugador disponible! Coloca una unidad y márcala como "Jugador". + Aucune unité joueur disponible ! Placez une unité et marquez-la en tant que "joueur". + Keine Spielereinheit verfügbar. Setze eine Einheit und markiere sie als "Spieler". + プレイヤーユニットがありません!ユニットを設置し"プレイヤー"に設定してください。 + 플레이어 유닛을 사용할 수 없습니다! 유닛을 놓고 "플레이어"라고 표시하십시오. + 沒有可用的玩家單位!請擺放一個單位並設定成"玩家" + 没有可用的玩家单位!请摆放一个单位并设定成“玩家”。 + Non ci sono giocatori! Poisziona una unità e impostala come "Giocatore". + Brak dostępnych jednostek gracza! Postaw jednostkę i oznacz ją jako "Gracz". + Нет доступных игроков! Разместите юнит и отметьте его как «Игрок» + Nenhuma unidade de jogador disponível! Coloque uma unidade e marque como "Player". + Žádná jednotka hráče není dostupná! Umístěte jednotku a nastavte ji jako "Hráč". + + + No loadouts to import. + No hay equipamientos para importar. + Aucun équipement à importer. + Keine Ausrüstungen zum Importieren + インポートする装備がありません。 + 가져올 로드아웃이 없습니다. + 沒有裝備被匯入 + 没有负载被导入。 + Non ci sono equipaggiamenti da importare. + Brak zestawów wyposażenia do zaimportowania. + Нет комплектов для импорта + Nenhum loadout para importar. + Žádné sadz vybavení k importu. + Hiç bir kıyafet içe aktarılmadı. + + + ACE Arsenal + ACE Arsenal + ACE-Arsenal + ACE 武器庫 + ACE 무기고 + ACE虛擬軍火庫 + ACE 虚拟军火库 + Arsenale ACE + Arsenał ACE + ACE Арсенал + ACE Arsenal + Arsenal ACE + ACE Arzenál + ACE Arsenal + + + Return to ACE Arsenal. + Volver al arsenal de ACE + Zurück zum ACE-Arsenal. + ACE 武器庫へ戻ります。 + ACE 무기고로 돌아가기 + 返回到ACE虛擬軍火庫 + 返回到 ACE 虚拟军火库。 + Torna all'arsenale ACE + Wróć do arsenału ACE. + Вернуться в ACE Арсенал + Voltar para o ACE Arsenal + Retour à l'arsenal ACE + Vrátit se do ACE Arzenálu + ACE Arsenal'e dön. + + + Use ACE Arsenal to try out different weapons and equipment. + Usar el arsenal de ACE para probar diferentes armas y equipamiento. + Verwende ACE-Arsenal und sieh dir verschiedene Waffen und Ausrüstung an und probiere sie aus. + 様々な武器と装備を試せるよう ACE 武器庫を使用します。 + ACE 무기고를 사용하여 다른 무기와 장비를 시험해보십시오. + 使用ACE虛擬軍火庫來嘗試不同的武器與裝備 + 使用 ACE 虚拟军火库来尝试不同的武器与装备。 + Usa l'arsenale ACE per provare armi ed equipaggiamenti vari. + Skorzystaj z arsenału ACE by wypróbować broń i ekwipunek. + Используйте ACE Arsenal, чтобы опробовать различное оружие и снаряжение. + Use o ACE Arsenal para experimentar diferentes armas e equipamentos. + Utilisez l'arsenal ACE pour essayer différentes armes ou équipements. + Použijte ACE Arzenál k vyzkoušní různých zbraní a výbavy. + + + Try weapons and equipment and create your own loadouts. + Probar armas y equipo y crear tus propios equipamientos. + Probiere verschiedene Waffen und Ausrüstung aus und stelle dir eigene Ausrüstungsprofile zusammen. + 様々な武器と装備を試して、あなただけの装備を作成してください。 + 무기와 장비를 사용해보고 자신의 로드아웃을 만듭니다. + 嘗試不同的武器與裝備來組合你個人的裝備配置 + 尝试不同的武器与装备来组合你个人的负载配置。 + Prova armi ed equipaggiamenti e creai i tuoi equipaggiamenti personalizzati. + Wypróbuj broń i ekwipunek i stwórz swoje własne zestawy wyposażenia. + Опробуйте оружие и снаряжение, создавайте собственные комплекты экипировки. + Experimente armas e equipamentos e crie seus próprios loadouts. + Essayez des armes et du matériel et créez vos propres sets d'équipement. + Vyzkoušejte zbraně a výbavu a vytvořte si vlastní sady vybavení. + + + Open the loadouts screen + Abrir la pantalla de equipamientos + Öffnet das Ausrüstungsmenü + Affiche les sets d'équipement. + 開啟裝備選單 + 开启负载菜单 + 装備画面を開く + Apri la pagina degli equipaggiamenti + Otwórz ekran zestawów + Открыть окно комплектов экипировки + Abre a tela de loadouts + Otevřít obrazovku se sadami vybavení + Kıyafetler ekranını aç + 로드아웃 화면 열기 + + + Export current / default loadouts + Exportar el equipamiento actual / predefinido + Exportiert aktuelles / standard Loadout + Exporte le set d'équipement actuel/les sets prédéfinis. + 匯出當前/預設的裝備 + 导出当前/预设的装备 + 現在/標準装備をエクスポートします + Esporta l'equipaggiamento attuale oppure la lista degli equipaggiamenti di base + Eksportuj obecne / domyślne zestawy wyposażenia + Экспорт комплектов экипировки + Exporta loadout atual / loadouts padrões + Exportovat současný/standardní sady vybavení + 현재/기본 로드아웃을 내보냅니다 + + + Import current / default loadouts + Importar el equipamiento actual / predefinido + Importiert aktuelles / standard Loadout + Importe le set d'équipement actuel/les sets prédéfinis. + 匯入當前/預設的裝備 + 导入当前/预设的负载 + 現在/標準装備をインポートします + Importa l'equipaggiamento attuale oppure la lista degli equipaggiamenti di base + Importuj obecne / domyślne zestawy wyposażenia + Импорт комплектов экипировки + Importa loadout atual / loadouts padrões + Importovat současný/standardní sady vybavení + 현재/기본 로드아웃 을 불러옵니다 + + + Potassium levels + Niveles de potasio + Kaliumspiegel + Taux de potassium + 钾水平 + 鉀水平 + Livello di potassio + Poziomy potasu + Уровень Калия + Níveis de Potássio + Úrovně draslíku + Potasyum seviyeleri + カリウム含有量 + 칼륨 레벨 + + + Magnification + 放大倍率 + Grossissement + Aumento + Ingrandimento + Powiększenie + Увеличение + Vergrößerung + Zvětšení + Aumento + 배율 + 放大倍数 + 拡大倍率 + Büyütme + + + Nightvision Support + Soporte de visión nocturna + Nachtsicht Unterstützung + 暗視装置への対応 + Wsparcie noktowizyjne + Supporto visore notturno + Поддержка ночного видения + Suporte de Visão Noturna + Support JVN + 夜視鏡支援 + 支持夜视仪 + Podpora nočního vidění + Gece Görüş Desteği + 야간투시 지원 + + + Primary supported + Primaria soportada + Primär unterstützt + プライマリが対応 + Wspierane przez broń główną + Primario supportato + Поддерживается осн. прицелом + Primária suportada + Primaire supportée + 主武器支援 + 主镜支持 + Hlavní část hledí podporuje + 주무기 지원 + + + Secondary supported + Secundaria soportada + Sekundär unterstützt + セカンダリが対応 + Wspierane przez broń drugorzędną + Secondario supportato + Поддерживается доп. прицелом + Secundária suportada + Secondaire supportée + 次要武器支援 + 副镜支持 + Vedlejší část hledí podporuje + 보조무기 지원 + + + Primary integrated + Primaria integrada + Primär Integriert + プライマリに内蔵 + Zintegrowane z bronią główną + Primario integrato + Интегрирован в осн. прицел + Primária integrada + Primaire intégrée + 整合主武器 + 主镜内置 + Integrováno do hlavní části hledí + 주무기 내장 + + + Thermal integrated + Termico integrato + 熱画像装置内蔵 + Интегрирован тепловизор. + 열화상 내장 + Thermique intégrée + Thermal integriert + Térmica integrada + + + Thermal & Primary integrated + Termico e Primario integrato + 熱画像装置内蔵・プライマリに内蔵 + Интегрирован тепловизор и осн.прицел. + 열화상과 주무기 내장 + Thermique et primaire intégrés + Thermal und in Primärwaffe integriert + Térmica y Primaria integrada + + + Not Supported + No soportada + Nicht unterstützt + 非対応 + Nie wspierane + Non supportato + Не поддерживается + Não suportado + Non supporté + 不支援 + 不支持 + Není podporováno + Desteklenmiyor + 지원되지 않음 + + + Vision Mode + Sichtmodus + 映像モード + Modalità Visiva + 視覺模式 + 视觉模式 + 보기 모드 + Mode de vision + Tryb Wizji + Режим видения + Modo de Visão + Režim sledování + Görüş Modu + Modo de visión + + + Normal + Normal + Normalna + Normal + Нормальное + Normální + Normal + Normale + Normale + 通常 + 일반 + 正常 + 正常 + Normal + + + Night + Nacht + Noc + Visão Norturna + Ночное + Noční + Nocturna + Notturno + Nocturne + 暗視 + 야간 + 夜视 + 夜視 + Gece + + + Thermal + Wärme + Termo + Térmica + Тепловизор + Termální + Térmica + Termico + Thermique + 熱画像 + 열상 + 热成像 + 熱成像 + Termal + + + Page + Página + Seite + Page + ページ + 页面 + 頁面 + Pagina + Strona + Стр. + Página + Stránka + Sayfa + 페이지 + + + Enable the faces / voices / insignias tabs + Habilitar las pestañas de caras / voces / insignias + Aktiviere die Gesichter-, Stimmen- und Abzeichenübersicht + Activer les onglets visages/voix/insignes + 顔/声/バッジ(記章)タブを有効化 + 启用脸谱/语音/徽章选项 + 啟用臉譜/聲音/徽章選項 + Abilita volti, voci e insegne + Aktywuj zakładki twarz / głos / insygnia + Вкл. вкладки: лица, голоса, эмблемы + Ativar as abas de rostos / vozes / insígnias + Povolit záložky s tvářemi, hlasy a insigniemi + Yüzler/sesler/peçler bölmelerini etkinleştir + 얼굴/음성/부대마크 탭 활성화 + + + Empty the selected container + Vaciar el contenedor seleccionado + Aktuellen Container leeren + Vider le conteneur selectionné + 選択されたコンテナは空です + 选择的容器是空的 + 清空選擇的箱子 + Svuota il contenitore selezionato + Opróżnij wybrany pojemnik + Очистить контейнер + Esvaziar o cointâiner selecionado + Vyprázdnit vybraný nosič + 선택한 보관함 비우기 + + + Exported class name to clipboard + Exportar el nombre de clase al portapapeles + Der Klassenname wurde in die Zwischenablage exportiert + Nom de classe exporté dans le presse papier. + クリップボードへクラスネームをエクスポート + 将类名复制到剪贴板 + 輸出 class name 到剪貼簿上 + Copiato il nome della classe negli appunti + Wyeksportowano nazwę klasy do schowka + Имя класса, экспортированного в буфер + O nome da classe foi exportado para a área de transferência + Jméno třídy exportováno do schránky + 클래스 이름 복사하기 + + + Mode + 模式 + Mode + Modo + Modalità + Tryb + Режим + Modus + Režim + Modo + 모드 + 模式 + モード + Mod + + + Whitelist + Biała lista (lista wybranych) + Lista blanca + Whitelist + Seznam povolených + Lista Branca + Liste blanche + Fehérlista + Вайтлист + Lista Bianca + 許可リスト + 화이트리스트 + 白名單 + 白名单 + Beyaz Liste + + + Blacklist + Lista negra + Blacklist + 禁止リスト + Lista Nera + Czarna lista (lista wykluczeń) + Чёрный список + Lista Negra + Liste noire + 黑名單 + 黑名单 + Seznam zakázaných + Kara Liste + 블랙리스트 + + + Items + 物品 + Objets + Objetos + Oggetti + Przedmioty + Предметы + Gegenstände + Předměty + Itens + 물품 + 物品 + アイテム + Eşyalar + + + Export current items list as an array for use in scripts + Exportar la lista actual de objetos como una tabla para su uso en scripts + Exportiert aktuelle Gegenstände als Array, um es in Scripten zu verwenden + スクリプト用に現在のアイテムリストをアレイでエクスポートします + Esporta l'attuale lista di elementi come un array, per essere usati negli script + Eksportuj obecną listę przedmiotów jako tablicę do wykorzystania w skryptach + Exportar a lista atual de itens como uma matriz para usar em scripts + Экспорт текущего списка предметов в виде массива для использования в скриптах + Exporte l'équipement actuel dans le presse-papier, sous la forme d'un tableau à utiliser dans les scripts. + 匯出目前的物品列表為陣列用於腳本編寫 + 导出目前的物品列表为排列以用于脚本编写 + Exportovat současný seznam předmětů jako pole pro použití ve skriptech + 스크립트에서 사용을 위해 현재 항목 목록을 배열로 내보내기 + + + Import items list array from clipboard (should be the same format as export) + Importar tabla de lista de objetos desde el portapapeles (debe ser el mismo formato que la lista exportada) + Importiert alles aus der Zwischenablage (Sollte im gleichen Format sein, wie beim Exportieren) + Zaimportuj listę przedmiotów ze schowka (lista musi być w tym samym formacie jak przy exporcie) + クリップボードからアイテムリストをアレイでインポートします (エクスポートと同じフォーマットである必要があります) + Импорт массива списка предметов из буфера (должен иметь тот же формат, что при экспорте) + Importar lista de itens da área de transferência (deve estar no mesmo formato que uma lista exportada) + Importe un tableau d'équipements depuis le presse-papier (le format doit être identique à celui de l'exportation). + 從剪貼簿匯入物品列表之陣列(應該與匯出的格式一樣) + 从剪贴板导入物品列表排列(应与导出的格式一样) + Importa elenco appunti (deve essere nello stesso formato di un elenco esportato) + Importovat pole se seznamem předmětů ze schránky (měl by být ve stejném formátu jako export) + 클립보드에서 항목 목록을 배열로 가져옵니다(내보내기와 동일한 형식이어야 함). + + + Add Compatible Items + Añadir objetos compatibles + Füge kompatible Gegenstände hinzu + Dodaj kompatybilne przedmioty + 対応アイテムを追加 + Добавить совместимые предметы + Adicionar itens compatíveis + Ajouter des objets compatibles + 增加相容的物品 + 添加兼容物品 + Aggiungi Oggetti Compatibili + Přidat kompatibilní předměty + 호환 아이템 추가 + + + Will automatically add compatible attachments or magazines (based on selected category) for all weapons in current items list + Añade automáticamente accesorios o cargadores (de la categoría seleccionada) a todas las armas de la lista de objetos + Es werden automatisch kompatible Aufsätze oder Magazine für alle ausgewählten Waffen hinzugefügt + Automatycznie doda kompatybilne dodatki oraz magazynki (odpowiednio do każdej kategorii) dla wszystkich broni na liście + 現在のアイテムリスト内の全武器に対応する アタッチメントと弾倉(選択したカテゴリに基づく)を自動的に追加します + Добавляет совместимые приспособления или магазины (в зависимости от выбранной категории) для всего оружия в текущем списке предметов + Irá automaticamente adicionar acessórios ou carregadores (baseado na categoria selecionada) para todas as armas na lista de itens atual + Ajoute automatiquement des accessoires ou des chargeurs compatibles (en fonction de la catégorie sélectionnée), pour toutes les armes de la liste actuelle. + 將會自動增加相容的配件以及彈匣(基於選擇的類型)至妳目前物品列表中的全部武器 + 将自动为当前物品列表中的所有武器添加兼容的配件或弹匣(基于选定的类别) + Aggiungerà automaticamente accessori o caricatori (in base alla categoria selezionata) per tutte le armi nell'elenco degli oggetti correnti + Automaticky přídá kompatibilní zásobníky (na základě vybrané kategorie) ro všechny zbraně v současném seznamu předmětů + 현재 아이템 목록에 있는 모든 무기에 해당하는부착물과 탄창(선택한 카테고리에 따라)을 자동으로 추가합니다. + + + Time to live + Tiempo de vida + Lebenszeit + Durée d'expiration + 効力持続時間 + Czas by żyć + Tempo di vita + Время действия + Time to live + 有效時間 + 有效时间 + Time to live + Bitme Süresi + 유효 시간 + + + Fuse Time + Retard avant détonation + Задержка детонации + Opoźnienie zapalnika + 信管設定時間 + Tempo di spoletta + Tiempo de espoleta + Detonationsverzögerung + 引信时间 + 신관 시간 + Atraso de detonação + + + Detonates on impact + Aufschlagzünder + Spoletta a impatto + Détonation à l'impact + Детонация от удара + Detonuj przy uderzeniu + 着発信管 + Detona mediante impacto + 碰炸引信 + 충격 신관 + Detona por impacto + + + Save Face + 얼굴 저장 + Сохранить лицо + Guardar Cara + Salva faccia + 顔の保存 + Zapisz Twarz + Gesicht Speichern + Sauvegarder le visage + Salvar Rosto + + + Save Voice + 목소리 저장 + Сохранить голос + Guardar Voz + Salva voce + 声の保存 + Zapisz Głos + Stimme Speichern + Sauvegarder la voix + Salvar Voz + + + Save Insignia + 계급장 저장 + Сохранить эмблему + Guardar Insignia + Salva insegna + バッジ(記章)の保存 + Zapisz Naszywkę + Insignia Speichern + Sauvegarder l'insigne + Salvar Insígnia + + + Descending + 降順 + Malejąco + Absteigend + Decrescente + 내림차순 + Décroissant + Decrescente + Нисходящий + Descendiente + + + Ascending + 昇順 + Rosnąco + Aufsteigend + Ascendente + 오름차순 + Croissant + Crescente + Восходящий + Ascendiente + + + Tools + Nástroje + Werkzeuge + Инструменты + Narzędzia + Strumenti + Herramientas + Outils + 工具 + ツール + 도구 + Ferramentas + 工具 + Araçlar + + + Ammo count + 弾薬数 + Ilość amunicji + Munitionszahl + Numero di colpi + 장탄 수 + Nombre de munitions + Quantidade de munição + Количество боеприпасов + Cantidad de munición + + + Illuminators + Illuminateurs + Illuminanti + Leuchtmittel + 조명 + Iluminadores + イルミネーター + Осветители + Iluminadores + + + Default to Favorites + お気に入りを標準に + Domyślnie do Ulubionych + Mostra solo Preferiti come predefinito + Standardmäßig auf Favoriten eingestellt + 즐겨찾기 기본값 + Favoris par défaut + Favoritos por padrão + По умолчанию - Избранное + Favoritos por defecto + + + Controls whether the ACE Arsenal defaults to showing all items or favorites. + ACE 武器庫が標準ですべてのアイテムを表示するか、お気に入りを表示するかを制御します。 + Kontroluje, czy ACE Arsenal domyślnie wyświetla wszystkie przedmioty, czy tylko ulubione. + Controlla se lo stato predefinito dell'arsenale ACE mostra tutti gli oggetti o solo i preferiti. + Steuert, ob das ACE Arsenal standardmäßig alle Gegenstände oder nur Favoriten anzeigt. + ACE 아스널이 기본적으로 모든 아이템 또는 즐겨찾기를 표시할 지 여부를 조정합니다. + Contrôle si l'arsenal ACE affiche par défaut tous les éléments ou les favoris. + Controla se o Arsenal ACE exibe por padrão todos os itens ou favoritos. + Определяет, будет ли в арсенале ACE по умолчанию отображаться все предметы или избранное. + Controla si el Arsenal de ACE muestra por defecto todos los objetos o sólo los favoritos + + + Favorites Color + お気に入りの色 + Kolor Ulubionych + Favoritenfarbe + Colore preferiti + 즐겨찾기 색상 + Couleurs favorites + Cor dos favoritos + Избранный цвет + Color de Favoritos + + + Highlight color for favorited items. + お気に入りアイテムのハイライト色。 + Kolor podświetlenia ulubionych elementów. + Hervorhebungsfarbe für Lieblingsgegenstände. + Colore che mette in mostra i preferiti nella lista. + 즐겨찾기한 아이템을 색상으로 강조합니다. + Met en surbrillance les éléments favoris. + Cor de destaque para itens favoritados. + Выделите цветом любимые предметы. + Color de marcado para los objetos favoritos + + + Switch between displaying all items or your favorites.\nDouble click while holding Shift to add or remove an item. + アイテムをすべて表示するかお気に入りのみを表示するかを切り替えます。\nアイテムをお気に入りに追加または削除するには、Shiftキーを押しながらダブルクリックします。 + Przełączanie między wyświetlaniem wszystkich przedmiotów lub tylko ulubionych. \nKliknij dwukrotnie, przytrzymując Shift, aby dodać lub usunąć przedmiot. + Wechseln Sie zwischen der Anzeige aller Elemente oder Ihrer Favoriten.\nDoppelklicken Sie bei gedrückter Umschalttaste, um ein Element hinzuzufügen oder zu entfernen. + Cambia tra mostrare tutti gli oggetti o solo i tuoi preferiti.\nShift + Doppio Click per aggiungere o rimuovere un preferito. + 모든 아이템을 표시하거나 즐겨찾기를 표시할 때 전환합니다\nShift 키를 누른 상태에서 두 번 클릭하여 아이템을 추가하거나 제거합니다. + Change entre l'affichage de tous les éléments ou de vos favoris.\nDouble-cliquez en maintenant la touche Maj enfoncée pour ajouter ou supprimer un élément. + Alterna entre a exibição de todos os itens ou seus favoritos.\nClique duas vezes enquanto mantém pressionada a tecla Shift para adicionar ou remover um item. + Переключайтесь между отображением всех элементов или ваших избранных.\nДважды щелкните, удерживая Shift, чтобы добавить или удалить элемент. + Alterna entre mostrar todos los objetos o sólo los favoritos.\nDoble click mientras se pulsa Shift para añadir o quitar un objeto. + + + Search\nCTRL + Click to enable live results + Suche\nSTRG + Click für Live-Aktualisierung während des Schreibens + Cerca\nCTRL + Click per modificare i risultati mentre scrivi + 検索\nCTRL + クリックで検索結果の即時表示を有効化 + 검색\nCtrl + 클릭으로 실시간 검색 결과를 활성화 + Поиск\nCtrl + Click для включения результатов в реальном времени + Recherche\nCTRL + clic pour modifier les résultats tout en écrivant + Buscar\nCTRL + Click habilita los objetos en directo + + +