Skip to content

Commit 9b37531

Browse files
committed
Add Android plurals CLI test and improve output
Added a test for Android plurals handling in the CLI. Improved the 'view' command output to explicitly print the translation type (Singular or Plural). Also updated dependencies to langcodec 0.4.1 and performed minor code style cleanups.
1 parent 9346968 commit 9b37531

5 files changed

Lines changed: 85 additions & 35 deletions

File tree

langcodec-cli/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "langcodec-cli"
3-
version = "0.4.0"
3+
version = "0.4.1"
44
edition = "2024"
55
description = "A universal CLI tool for converting and inspecting localization files (Apple, Android, CSV, etc.)"
66
license = "MIT"
@@ -12,7 +12,7 @@ categories = ["command-line-utilities", "internationalization", "localization"]
1212
documentation = "https://docs.rs/langcodec-cli"
1313

1414
[dependencies]
15-
langcodec = "0.4.0"
15+
langcodec = "0.4.1"
1616
clap = { version = "4", features = ["derive"] }
1717
clap_complete = "4"
1818
unicode-width = "0.2.1"

langcodec-cli/src/convert.rs

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::formats::{self, parse_custom_format};
22
use crate::transformers::custom_format_to_resource;
33
use crate::validation::{self, validate_custom_format_file};
44

5-
use langcodec::{convert_auto, formats::FormatType, Codec};
5+
use langcodec::{Codec, convert_auto, formats::FormatType};
66
use std::fs::File;
77
use std::io::BufWriter;
88

@@ -125,8 +125,7 @@ pub fn run_unified_convert_command(input: String, output: String, options: Conve
125125
}
126126

127127
// If exclude_lang is specified, exclude those languages
128-
if !options.exclude_lang.is_empty() && options.exclude_lang.contains(lang)
129-
{
128+
if !options.exclude_lang.is_empty() && options.exclude_lang.contains(lang) {
130129
return false;
131130
}
132131

@@ -138,18 +137,19 @@ pub fn run_unified_convert_command(input: String, output: String, options: Conve
138137
},
139138
) {
140139
Ok(()) => {
141-
let filter_msg = if !options.include_lang.is_empty() || !options.exclude_lang.is_empty() {
142-
let mut parts = Vec::new();
143-
if !options.include_lang.is_empty() {
144-
parts.push(format!("including: {}", options.include_lang.join(", ")));
145-
}
146-
if !options.exclude_lang.is_empty() {
147-
parts.push(format!("excluding: {}", options.exclude_lang.join(", ")));
148-
}
149-
format!(" with language filtering ({})", parts.join(", "))
150-
} else {
151-
String::new()
152-
};
140+
let filter_msg =
141+
if !options.include_lang.is_empty() || !options.exclude_lang.is_empty() {
142+
let mut parts = Vec::new();
143+
if !options.include_lang.is_empty() {
144+
parts.push(format!("including: {}", options.include_lang.join(", ")));
145+
}
146+
if !options.exclude_lang.is_empty() {
147+
parts.push(format!("excluding: {}", options.exclude_lang.join(", ")));
148+
}
149+
format!(" with language filtering ({})", parts.join(", "))
150+
} else {
151+
String::new()
152+
};
153153

154154
println!(
155155
"✅ Successfully converted to .langcodec (Resource JSON array){}",
@@ -158,18 +158,19 @@ pub fn run_unified_convert_command(input: String, output: String, options: Conve
158158
return;
159159
}
160160
Err(e) => {
161-
let filter_msg = if !options.include_lang.is_empty() || !options.exclude_lang.is_empty() {
162-
let mut parts = Vec::new();
163-
if !options.include_lang.is_empty() {
164-
parts.push(format!("including: {}", options.include_lang.join(", ")));
165-
}
166-
if !options.exclude_lang.is_empty() {
167-
parts.push(format!("excluding: {}", options.exclude_lang.join(", ")));
168-
}
169-
format!(" with language filtering ({})", parts.join(", "))
170-
} else {
171-
String::new()
172-
};
161+
let filter_msg =
162+
if !options.include_lang.is_empty() || !options.exclude_lang.is_empty() {
163+
let mut parts = Vec::new();
164+
if !options.include_lang.is_empty() {
165+
parts.push(format!("including: {}", options.include_lang.join(", ")));
166+
}
167+
if !options.exclude_lang.is_empty() {
168+
parts.push(format!("excluding: {}", options.exclude_lang.join(", ")));
169+
}
170+
format!(" with language filtering ({})", parts.join(", "))
171+
} else {
172+
String::new()
173+
};
173174

174175
println!("❌ Conversion to .langcodec failed{}", filter_msg);
175176
eprintln!("Error: {}", e);
@@ -186,7 +187,10 @@ pub fn run_unified_convert_command(input: String, output: String, options: Conve
186187
}
187188

188189
// Strategy 2: Try custom formats for JSON/YAML/langcodec files
189-
if input.ends_with(".json") || input.ends_with(".yaml") || input.ends_with(".yml") || input.ends_with(".langcodec")
190+
if input.ends_with(".json")
191+
|| input.ends_with(".yaml")
192+
|| input.ends_with(".yml")
193+
|| input.ends_with(".langcodec")
190194
{
191195
// For JSON files without explicit format, try standard format detection first
192196
if input.ends_with(".json") && options.input_format.is_none() {
@@ -557,4 +561,3 @@ pub fn read_resources_from_any_input(
557561
input
558562
))
559563
}
560-

langcodec-cli/src/merge.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ pub fn run_merge_command(
9898
// Set version field in the resources to make sure xcstrings format would not throw an error
9999
let version = version_override.unwrap_or_else(|| {
100100
codec
101-
.resources
102-
.first()
103-
.and_then(|r| r.metadata.custom.get("version").cloned())
104-
.unwrap_or_else(|| "1.0".to_string())
101+
.resources
102+
.first()
103+
.and_then(|r| r.metadata.custom.get("version").cloned())
104+
.unwrap_or_else(|| "1.0".to_string())
105105
});
106106

107107
println!("Setting metadata.version to: {}", version);

langcodec-cli/src/view.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub fn print_view(codec: &Codec, lang_filter: &Option<String>, full: bool) {
5656

5757
match &entry.value {
5858
langcodec::types::Translation::Singular(value) => {
59+
println!(" Type: Singular");
5960
if full {
6061
println!(" Value: {}", value);
6162
} else {
@@ -68,6 +69,7 @@ pub fn print_view(codec: &Codec, lang_filter: &Option<String>, full: bool) {
6869
}
6970
}
7071
langcodec::types::Translation::Plural(plural) => {
72+
println!(" Type: Plural");
7173
println!(" Plural ID: {}", plural.id);
7274
for (category, value) in &plural.forms {
7375
if full {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use std::fs;
2+
use std::process::Command;
3+
use tempfile::TempDir;
4+
5+
#[test]
6+
fn test_cli_view_android_plurals() {
7+
let temp_dir = TempDir::new().unwrap();
8+
let input_file = temp_dir.path().join("strings.xml");
9+
10+
let xml = r#"
11+
<resources>
12+
<plurals name="apples" translatable="true">
13+
<item quantity="one">One apple</item>
14+
<item quantity="other">%d apples</item>
15+
</plurals>
16+
</resources>
17+
"#;
18+
fs::write(&input_file, xml).unwrap();
19+
20+
let output = Command::new("cargo")
21+
.args([
22+
"run",
23+
"--quiet",
24+
"--",
25+
"view",
26+
"-i",
27+
input_file.to_str().unwrap(),
28+
"--full",
29+
])
30+
.output()
31+
.unwrap();
32+
33+
assert!(
34+
output.status.success(),
35+
"CLI failed: {}",
36+
String::from_utf8_lossy(&output.stderr)
37+
);
38+
39+
let stdout = String::from_utf8_lossy(&output.stdout);
40+
// Check plural structure is printed
41+
assert!(stdout.contains("Type: Plural"));
42+
assert!(stdout.contains("Plural ID: apples"));
43+
assert!(stdout.contains("One apple") || stdout.contains("one"));
44+
assert!(stdout.contains("%d apples") || stdout.contains("other"));
45+
}

0 commit comments

Comments
 (0)