mod defaults;
mod schema;
pub use schema::*;
use anyhow::{bail, Result};
use std::path::Path;
pub fn load_config(path: &Path) -> Result<Config> {
if !path.exists() {
bail!(
"Configuration file not found: {}\n\
\n\
Hint: Run `slang-roblox init` to create a default configuration.\n\
Or create the file manually with:\n\
\n\
base_locale: en\n\
supported_locales:\n\
- en\n\
input_directory: translations\n\
output_directory: output",
path.display()
);
}
let content = std::fs::read_to_string(path).map_err(|e| {
anyhow::anyhow!(
"Failed to read configuration file: {}\n\
Error: {}\n\
\n\
Hint: Check file permissions and ensure the file is readable.",
path.display(),
e
)
})?;
if content.trim().is_empty() {
bail!(
"Configuration file is empty: {}\n\
\n\
Hint: Run `slang-roblox init` to create a default configuration.",
path.display()
);
}
let config: Config = serde_yaml::from_str(&content).map_err(|e| {
let location = e.location();
let (line, column) = if let Some(loc) = location {
(loc.line(), loc.column())
} else {
(0, 0)
};
let lines: Vec<&str> = content.lines().collect();
let context_line = if line > 0 && line <= lines.len() {
lines[line - 1]
} else {
""
};
anyhow::anyhow!(
"Failed to parse configuration file: {}\n\
Error at line {}, column {}: {}\n\
\n\
Problematic line:\n\
{}\n\
{}^\n\
\n\
Common configuration errors:\n\
- Incorrect indentation (use 2 spaces)\n\
- Missing colon after key\n\
- Wrong field names (check spelling)\n\
\n\
Hint: Compare with the example in the documentation or run `slang-roblox init`.",
path.display(),
line,
column,
e,
context_line,
" ".repeat(column.saturating_sub(1))
)
})?;
config.validate().map_err(|e| {
anyhow::anyhow!(
"{}\n\
\n\
Configuration file: {}",
e,
path.display()
)
})?;
Ok(config)
}
pub fn create_default_config(path: &Path) -> Result<()> {
let yaml = r#"# Roblox Slang Configuration
# Documentation: https://github.com/mathtechstudio/roblox-slang
# Base locale (fallback when translation is missing)
base_locale: en
# Supported locales for your game
supported_locales:
- en
# Directory containing translation files (JSON/YAML)
input_directory: translations
# Directory for generated Luau code
output_directory: output
# Optional: Namespace for generated module (null = no namespace)
namespace: null
# Optional: Translation overrides
# overrides:
# enabled: true
# file: overrides.yaml
# Optional: Analytics for tracking missing translations
# analytics:
# enabled: true
# track_missing: true
# track_usage: true
# Optional: Roblox Cloud integration for syncing translations
# Enables upload, download, and bidirectional sync with Roblox Cloud Localization Tables
# cloud:
# # Localization table ID (UUID format)
# # Get from: Creator Dashboard > Localization > Table Settings
# table_id: "your-table-id-here"
#
# # Game/Universe ID (numeric)
# # Get from: Creator Dashboard > Game Settings > Basic Info
# game_id: "your-game-id-here"
#
# # API Key (RECOMMENDED: Use environment variable instead)
# # Set via: export ROBLOX_CLOUD_API_KEY=your_key_here
# # Get from: https://create.roblox.com/credentials
# # api_key: "your-api-key-here"
#
# # Default merge strategy for sync command
# # Options:
# # - merge: Upload local-only, download cloud-only, prefer cloud for conflicts (recommended)
# # - overwrite: Replace all cloud translations with local (destructive)
# # - skip-conflicts: Only sync non-conflicting entries
# strategy: merge
"#;
std::fs::write(path, yaml).map_err(|e| {
anyhow::anyhow!(
"Failed to write configuration file: {}\n\
Error: {}\n\
\n\
Hint: Check directory permissions and ensure you have write access.",
path.display(),
e
)
})?;
Ok(())
}
pub fn create_default_overrides(path: &Path) -> Result<()> {
let yaml = r#"# Translation Overrides
# Override specific translations per locale without modifying source files
# Useful for A/B testing, seasonal events, or quick fixes
# Example overrides:
# en:
# ui.buttons.buy: "Purchase Now!"
# ui.messages.greeting: "Hey there, {name}!"
#
# es:
# ui.buttons.buy: "¡Comprar Ahora!"
#
# id:
# ui.buttons.buy: "Beli Sekarang!"
"#;
std::fs::write(path, yaml).map_err(|e| {
anyhow::anyhow!(
"Failed to write overrides file: {}\n\
Error: {}\n\
\n\
Hint: Check directory permissions and ensure you have write access.",
path.display(),
e
)
})?;
Ok(())
}