Skip to content

Commit 4518ba6

Browse files
feat: add PHP and Blade language support
1 parent 1927277 commit 4518ba6

File tree

5 files changed

+546
-6
lines changed

5 files changed

+546
-6
lines changed

crates/intl-lens-extension/extension.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ repository = "https://github.com/nguyenphutrong/intl-lens"
88

99
[language_servers.intl-lens]
1010
name = "Intl Lens Language Server"
11-
languages = ["TypeScript", "TSX", "JavaScript", "JSX", "HTML", "Angular"]
11+
languages = ["TypeScript", "TSX", "JavaScript", "JSX", "HTML", "Angular", "PHP", "Blade"]
1212

1313
[language_servers.intl-lens.language_ids]
1414
"TypeScript" = "typescript"
@@ -17,3 +17,5 @@ languages = ["TypeScript", "TSX", "JavaScript", "JSX", "HTML", "Angular"]
1717
"JSX" = "javascriptreact"
1818
"HTML" = "html"
1919
"Angular" = "html"
20+
"PHP" = "php"
21+
"Blade" = "blade"

crates/intl-lens/src/backend.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ impl I18nBackend {
122122
scheme: None,
123123
pattern: None,
124124
},
125+
DocumentFilter {
126+
language: Some("php".to_string()),
127+
scheme: None,
128+
pattern: None,
129+
},
130+
DocumentFilter {
131+
language: Some("blade".to_string()),
132+
scheme: None,
133+
pattern: None,
134+
},
125135
]);
126136

127137
let register_options = InlayHintRegistrationOptions {

crates/intl-lens/src/config.rs

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
use std::collections::HashSet;
12
use std::path::Path;
23

34
use serde::{Deserialize, Serialize};
5+
use serde_json::Value;
46

57
#[derive(Debug, Clone, Serialize, Deserialize)]
68
#[serde(rename_all = "camelCase")]
@@ -52,15 +54,44 @@ impl I18nConfig {
5254

5355
for config_path in config_paths {
5456
if let Ok(content) = std::fs::read_to_string(&config_path) {
55-
if let Ok(config) = serde_json::from_str::<I18nConfig>(&content) {
57+
let raw_config = serde_json::from_str::<Value>(&content).ok();
58+
59+
if let Ok(mut config) = serde_json::from_str::<I18nConfig>(&content) {
60+
let has_locale_paths = raw_config
61+
.as_ref()
62+
.and_then(|value| value.as_object())
63+
.is_some_and(|object| {
64+
object.contains_key("localePaths") || object.contains_key("locale_paths")
65+
});
66+
67+
if !has_locale_paths {
68+
config.add_detected_locale_paths(root);
69+
}
70+
5671
tracing::info!("Loaded config from {:?}", config_path);
5772
return config;
5873
}
5974
}
6075
}
6176

6277
tracing::info!("Using default config");
63-
Self::default()
78+
let mut config = Self::default();
79+
config.add_detected_locale_paths(root);
80+
config
81+
}
82+
83+
fn add_detected_locale_paths(&mut self, root: &Path) {
84+
let detected_paths = detect_framework_locale_paths(root);
85+
if detected_paths.is_empty() {
86+
return;
87+
}
88+
89+
let mut existing: HashSet<String> = self.locale_paths.iter().cloned().collect();
90+
for path in detected_paths {
91+
if existing.insert(path.clone()) {
92+
self.locale_paths.push(path);
93+
}
94+
}
6495
}
6596
}
6697

@@ -93,5 +124,66 @@ fn default_function_patterns() -> Vec<String> {
93124
r#"translate(?:Service)?\.(?:instant|get|stream)\s*\(\s*["']([^"']+)["']"#.to_string(),
94125
r#"transloco(?:Service)?\.(?:translate|selectTranslate)\s*\(\s*["']([^"']+)["']"#.to_string(),
95126
r#"["']([^"']+)["']\s*\|\s*(?:translate|transloco)\b"#.to_string(),
127+
r#"__\s*\(\s*["']([^"']+)["']"#.to_string(),
128+
r#"trans(?:_choice)?\s*\(\s*["']([^"']+)["']"#.to_string(),
129+
r#"Lang::(?:get|choice)\s*\(\s*["']([^"']+)["']"#.to_string(),
130+
r#"@lang\s*\(\s*["']([^"']+)["']"#.to_string(),
131+
r#"@choice\s*\(\s*["']([^"']+)["']"#.to_string(),
96132
]
97133
}
134+
135+
fn detect_framework_locale_paths(root: &Path) -> Vec<String> {
136+
let mut paths = Vec::new();
137+
138+
if is_angular_project(root) {
139+
paths.push("src/assets/i18n".to_string());
140+
}
141+
142+
if is_laravel_project(root) {
143+
paths.push("resources/lang".to_string());
144+
paths.push("lang".to_string());
145+
}
146+
147+
paths
148+
.into_iter()
149+
.filter(|path| root.join(path).exists())
150+
.collect()
151+
}
152+
153+
fn is_angular_project(root: &Path) -> bool {
154+
let package_json = root.join("package.json");
155+
let Some(value) = read_json(&package_json) else {
156+
return false;
157+
};
158+
159+
json_has_dependency(&value, "@angular/core", &["dependencies", "devDependencies"])
160+
|| json_has_dependency(&value, "@angular/cli", &["dependencies", "devDependencies"])
161+
}
162+
163+
fn is_laravel_project(root: &Path) -> bool {
164+
let composer_json = root.join("composer.json");
165+
let Some(value) = read_json(&composer_json) else {
166+
return false;
167+
};
168+
169+
json_has_dependency(&value, "laravel/framework", &["require", "require-dev"])
170+
|| json_has_name(&value, "laravel/laravel")
171+
}
172+
173+
fn read_json(path: &Path) -> Option<Value> {
174+
let content = std::fs::read_to_string(path).ok()?;
175+
serde_json::from_str::<Value>(&content).ok()
176+
}
177+
178+
fn json_has_dependency(value: &Value, dependency: &str, sections: &[&str]) -> bool {
179+
sections.iter().any(|section| {
180+
value
181+
.get(*section)
182+
.and_then(|deps| deps.as_object())
183+
.is_some_and(|deps| deps.contains_key(dependency))
184+
})
185+
}
186+
187+
fn json_has_name(value: &Value, name: &str) -> bool {
188+
value.get("name").and_then(|v| v.as_str()) == Some(name)
189+
}

0 commit comments

Comments
 (0)