use linguini_ir::{IrMessage, IrModule};
use super::names::{escape_comment, escape_string, function_name, property_key, ts_type};
use super::tree::{nested_message_tree, MessageTree};
use super::TypeScriptOptions;
pub fn generate_shared_declaration() -> String {
let mut output = String::new();
output.push_str("export type FormatterOptions = Record<string, string>;\n\n");
output.push_str("export declare function formatCurrency(\n");
output.push_str(" value: number | string,\n");
output.push_str(" locale: string,\n");
output.push_str(" options?: FormatterOptions,\n");
output.push_str("): string;\n\n");
output.push_str("export declare function formatDate(\n");
output.push_str(" value: Date | number | string,\n");
output.push_str(" locale: string,\n");
output.push_str(" options?: FormatterOptions,\n");
output.push_str("): string;\n\n");
output.push_str("export declare function selectBranch(\n");
output.push_str(" key: string,\n");
output.push_str(" branches: Record<string, string>,\n");
output.push_str("): string;\n");
output
}
pub fn generate_index_declaration(options: &TypeScriptOptions) -> String {
let locale = options.locale.replace('-', "_");
let locale_path = escape_string(&options.locale);
let locale_literal = format!("\"{}\"", escape_string(&options.locale));
let mut output = String::new();
output.push_str(&format!(
"import {locale} from \"./locales/{locale_path}\";\n\n"
));
output.push_str(&format!(
"declare const localeModules: {{ readonly {locale}: typeof {locale} }};\n\n"
));
output.push_str("type LinguiniLanguage = keyof typeof localeModules;\n");
output.push_str("export type Linguini = (typeof localeModules)[LinguiniLanguage];\n\n");
output.push_str(&format!(
"type LinguiniLanguageInput = LinguiniLanguage | {locale_literal};\n\n"
));
output.push_str(
"export declare function createLinguini(language: LinguiniLanguageInput): Linguini;\n\n",
);
output.push_str("export declare function createLinguiniProvider(options: {\n");
output.push_str(" resolveLanguage: () => LinguiniLanguageInput;\n");
output.push_str("}): Linguini;\n\n");
output.push_str("export declare function configureLinguini(options: {\n");
output.push_str(" language: LinguiniLanguageInput | (() => LinguiniLanguageInput);\n");
output.push_str("}): Linguini;\n\n");
output.push_str("export declare const lgl: Linguini;\n");
output
}
pub fn generate_locale_declaration(schema: &IrModule) -> String {
let mut output = String::new();
emit_type_declarations(schema, &mut output);
let exports = emit_message_declarations(schema, &mut output);
emit_default_declaration(&exports, &mut output);
output
}
fn emit_type_declarations(schema: &IrModule, output: &mut String) {
for item in &schema.enums {
for doc in &item.docs {
output.push_str(&format!("/** {} */\n", escape_comment(doc)));
}
let variants = item
.variants
.iter()
.map(|variant| format!("\"{}\"", escape_string(variant)))
.collect::<Vec<_>>()
.join(" | ");
output.push_str(&format!("export type {} = {variants};\n\n", item.name));
}
for item in &schema.type_aliases {
for doc in &item.docs {
output.push_str(&format!("/** {} */\n", escape_comment(doc)));
}
output.push_str(&format!(
"export type {} = {};\n\n",
item.name,
ts_type(&item.target)
));
}
}
fn emit_message_declarations(schema: &IrModule, output: &mut String) -> Vec<String> {
let nested = nested_message_tree(schema);
let mut exports = Vec::new();
for signature in &schema.messages {
if signature.name.contains('.') {
continue;
}
emit_function_declaration(signature, output);
exports.push(function_name(&signature.name));
}
for (group, messages) in nested.children {
emit_message_object_declaration(&group, &messages, output);
exports.push(group);
}
exports
}
fn emit_message_object_declaration(name: &str, tree: &MessageTree, output: &mut String) {
output.push_str(&format!("export declare const {name}: "));
emit_object_type(tree, 0, output);
output.push_str(";\n\n");
}
fn emit_object_type(tree: &MessageTree, depth: usize, output: &mut String) {
let indent = " ".repeat(depth);
let child_indent = " ".repeat(depth + 1);
output.push_str("{\n");
for entry in &tree.messages {
output.push_str(&format!(
"{child_indent}readonly {}: {};\n",
property_key(&entry.property),
group_property_type(&entry.signature)
));
}
for (name, child) in &tree.children {
output.push_str(&format!("{child_indent}readonly {}: ", property_key(name)));
emit_object_type(child, depth + 1, output);
output.push_str(";\n");
}
output.push_str(&indent);
output.push('}');
}
fn emit_function_declaration(signature: &IrMessage, output: &mut String) {
for doc in &signature.docs {
output.push_str(&format!("/** {} */\n", escape_comment(doc)));
}
output.push_str(&format!(
"export declare function {}({}): string;\n\n",
function_name(&signature.name),
signature_params(signature)
));
}
fn group_property_type(signature: &IrMessage) -> String {
if signature.parameters.is_empty() {
"string".to_owned()
} else {
format!("({}) => string", signature_params(signature))
}
}
fn emit_default_declaration(exports: &[String], output: &mut String) {
output.push_str("declare const lgl: {\n");
for name in exports {
output.push_str(&format!(" readonly {name}: typeof {name};\n"));
}
output.push_str("};\n\n");
output.push_str("export default lgl;\n");
}
fn signature_params(signature: &IrMessage) -> String {
signature
.parameters
.iter()
.map(|parameter| format!("{}: {}", parameter.name, ts_type(¶meter.ty)))
.collect::<Vec<_>>()
.join(", ")
}