Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/ExtensionManagement/
NLSResolver.rs

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