use super::Emitter;
use crate::ir::Ir;
pub struct ReactEmitter {
pub core: String,
}
const TEMPLATE: &str = r#"// AUTO-GENERATED by stele — do not edit.
// Reactive React / React Native bindings over the generated copy.
// Changing the locale via setLocale re-renders every useCopy() consumer.
import {
createContext,
createElement,
useContext,
useMemo,
useState,
type ReactNode,
} from "react";
import { createCopy, type Copy, type Locale } from "__CORE__";
type CopyContextValue = {
copy: Copy;
locale: Locale;
setLocale: (locale: Locale) => void;
};
const CopyContext = createContext<CopyContextValue | null>(null);
export function CopyProvider(props: { locale: Locale; children: ReactNode }) {
const [locale, setLocale] = useState<Locale>(props.locale);
const copy = useMemo(() => createCopy(locale), [locale]);
const value = useMemo(() => ({ copy, locale, setLocale }), [copy, locale]);
return createElement(CopyContext.Provider, { value }, props.children);
}
export function useCopy(): Copy {
const ctx = useContext(CopyContext);
if (ctx === null) {
throw new Error("useCopy must be used within a <CopyProvider>");
}
return ctx.copy;
}
export function useLocale(): [Locale, (locale: Locale) => void] {
const ctx = useContext(CopyContext);
if (ctx === null) {
throw new Error("useLocale must be used within a <CopyProvider>");
}
return [ctx.locale, ctx.setLocale];
}
"#;
impl Emitter for ReactEmitter {
fn emit(&self, _ir: &Ir) -> String {
TEMPLATE.replace("__CORE__", &self.core)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;
fn empty_ir() -> Ir {
Ir {
canonical: "en".into(),
locales: vec!["en".into()],
messages: vec![],
plural_rules: BTreeMap::new(),
}
}
#[test]
fn substitutes_core_and_exports_bindings() {
let out = ReactEmitter {
core: "./my-copy".into(),
}
.emit(&empty_ir());
assert!(out.contains("from \"./my-copy\""));
assert!(out.contains("export function CopyProvider"));
assert!(out.contains("export function useCopy"));
assert!(out.contains("export function useLocale"));
}
}