Skip to main content

Mountain/ExtensionManagement/
NLSResolver.rs

1#![allow(unused_variables, dead_code, unused_imports)]
2
3//! NLS (National Language Support) placeholder resolution for extension
4//! manifests. VS Code extensions embed `%key%` tokens in their `package.json`
5//! that are resolved at runtime from a `package.nls.json` bundle.
6//!
7//! Three functions work together:
8//! - `ManifestContainsNLSPlaceholders` - fast pre-scan to skip bundle I/O
9//! - `LoadNLSBundle` - read and parse `package.nls.json`
10//! - `ResolveNLSPlaceholdersInner` - in-place recursive token substitution
11
12use std::{path::PathBuf, sync::Arc};
13
14use CommonLibrary::{Effect::ApplicationRunTime::ApplicationRunTime as _, FileSystem::ReadFile::ReadFile};
15use serde_json::{Map, Value};
16
17use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, dev_log};
18
19/// Return `true` if `Value` contains any `%placeholder%` token anywhere in
20/// the tree. Used to skip bundle I/O for manifests that have no tokens.
21pub fn ManifestContainsNLSPlaceholders(Value:&Value) -> bool {
22	match Value {
23		serde_json::Value::String(Text) => {
24			Text.len() >= 2 && Text.starts_with('%') && Text.ends_with('%') && !Text[1..Text.len() - 1].contains('%')
25		},
26
27		serde_json::Value::Array(Items) => Items.iter().any(ManifestContainsNLSPlaceholders),
28
29		serde_json::Value::Object(Object) => Object.values().any(ManifestContainsNLSPlaceholders),
30
31		_ => false,
32	}
33}
34
35/// Load an extension's NLS bundle (`package.nls.json`) into a `{key → string}`
36/// map. Returns `None` if absent or unreadable - placeholders remain as-is.
37/// Entries can be bare strings or `{message, comment}` objects; only `message`
38/// is kept. The `PlaceholdersNeeded` flag downgrades the "no bundle" warning
39/// when the manifest has no `%placeholder%` entries (absence is benign).
40pub async fn LoadNLSBundle(
41	RunTime:&Arc<ApplicationRunTime>,
42
43	ExtensionPath:&PathBuf,
44
45	PlaceholdersNeeded:bool,
46) -> Option<Map<String, Value>> {
47	let NLSPath = ExtensionPath.join("package.nls.json");
48
49	let Content = match RunTime.Run(ReadFile(NLSPath.clone())).await {
50		Ok(Bytes) => Bytes,
51
52		Err(Error) => {
53			if PlaceholdersNeeded {
54				dev_log!("nls", "[LandFix:NLS] no bundle for {} ({})", ExtensionPath.display(), Error);
55			} else {
56				dev_log!(
57					"nls",
58					"[LandFix:NLS] {} has no placeholders, no bundle needed",
59					ExtensionPath.display()
60				);
61			}
62
63			return None;
64		},
65	};
66
67	let Parsed:Value = match serde_json::from_slice(&Content) {
68		Ok(V) => V,
69
70		Err(Error) => {
71			dev_log!("nls", "warn: [LandFix:NLS] failed to parse {}: {}", NLSPath.display(), Error);
72
73			return None;
74		},
75	};
76
77	let Object = Parsed.as_object()?;
78
79	let mut Resolved = Map::with_capacity(Object.len());
80
81	for (Key, RawValue) in Object {
82		let Text = if let Some(s) = RawValue.as_str() {
83			Some(s.to_string())
84		} else if let Some(obj) = RawValue.as_object() {
85			obj.get("message").and_then(|m| m.as_str()).map(|s| s.to_string())
86		} else {
87			None
88		};
89
90		if let Some(t) = Text {
91			Resolved.insert(Key.clone(), Value::String(t));
92		}
93	}
94
95	dev_log!(
96		"nls",
97		"[LandFix:NLS] loaded {} keys for {}",
98		Resolved.len(),
99		ExtensionPath.display()
100	);
101
102	Some(Resolved)
103}
104
105/// In-place recursive substitution of `%key%` tokens using the NLS map.
106/// `Replaced` and `Unresolved` accumulate counts for the outer scanner's
107/// one-line summary log.
108pub fn ResolveNLSPlaceholdersInner(Value:&mut Value, NLS:&Map<String, Value>, Replaced:&mut u32, Unresolved:&mut u32) {
109	match Value {
110		serde_json::Value::String(Text) => {
111			if Text.len() >= 2 && Text.starts_with('%') && Text.ends_with('%') {
112				let Key = &Text[1..Text.len() - 1];
113
114				if !Key.is_empty() && !Key.contains('%') {
115					if let Some(Replacement) = NLS.get(Key).and_then(|v| v.as_str()) {
116						*Text = Replacement.to_string();
117						*Replaced += 1;
118					} else {
119						*Unresolved += 1;
120					}
121				}
122			}
123		},
124
125		serde_json::Value::Array(Items) => {
126			for Item in Items {
127				ResolveNLSPlaceholdersInner(Item, NLS, Replaced, Unresolved);
128			}
129		},
130
131		serde_json::Value::Object(Map) => {
132			for (_, FieldValue) in Map {
133				ResolveNLSPlaceholdersInner(FieldValue, NLS, Replaced, Unresolved);
134			}
135		},
136
137		_ => {},
138	}
139}