Layouts

Page layouts wrap content with common structure (nav, footer).

Basic Layout

public class Layout implements Template {
    private final String title;
    private final Element content;

    public Layout(String title, Element content) {
        this.title = title;
        this.content = content;
    }

    public Element render() {
        return html(
            head(title(title)),
            body(new Nav(), main(content), new Footer())
        );
    }
}

Register Layout

// In Routes.java - set default layout
app.layout(Layout.class);

// Pages automatically wrapped
app.pages(
    "/", HomePage.class,
    "/about", AboutPage.class
);

Theme Tokens

public final class Theme {
    // Colors
    public static final CSSValue PRIMARY = hex("#6366f1");
    public static final CSSValue TEXT = hex("#1e293b");

    // Spacing
    public static final CSSValue SP_4 = rem(1);
    public static final CSSValue SP_8 = rem(2);
}

// Usage
div(attrs().style().color(PRIMARY).padding(SP_4).done())
Define design tokens in Theme.java for consistent styling across your app.

Internationalization (i18n)

Build multilingual applications with translated messages.

import com.osmig.Jweb.framework.i18n.I18n;

// Get translated message
String title = I18n.t("page.title");

// With parameters
String greeting = I18n.t("welcome", username);
// "welcome" = "Hello, {0}!" → "Hello, John!"

Message Files

Create message files in src/main/resources/messages/:

// messages_en.properties
page.title=Welcome
nav.home=Home
nav.about=About
welcome=Hello, {0}!
errors.required=This field is required

// messages_es.properties
page.title=Bienvenido
nav.home=Inicio
nav.about=Acerca de
welcome=¡Hola, {0}!
errors.required=Este campo es obligatorio

In Templates

public class HomePage implements Template {
    public Element render() {
        return div(
            h1(I18n.t("page.title")),
            nav(
                a(href("/"), I18n.t("nav.home")),
                a(href("/about"), I18n.t("nav.about"))
            )
        );
    }
}

Locale Detection

I18n detects locale from (in order):

// 1. Query parameter: /page?lang=es
// 2. Session attribute: lang
// 3. Cookie: lang
// 4. Accept-Language header
// 5. Default locale

// Get current locale
Locale locale = I18n.getLocale(req);

// Use middleware for automatic detection
app.use(I18n.middleware());

Language Picker

// Get supported locales
List<I18n.LocaleInfo> locales = I18n.getSupportedLocales();
// Each has: code, nativeName, englishName

// In template
form(method("post"), action("/language"),
    select(name("lang"), onChange("this.form.submit()"),
        each(locales, locale ->
            option(
                value(locale.code()),
                selected(locale.code().equals(I18n.current().getLanguage())),
                text(locale.nativeName())  // "Español" for Spanish
            )
        )
    )
)

Switch Language

// Via session
app.post("/language", req -> {
    String lang = req.formParam("lang");
    req.session().setAttribute("lang", lang);
    return Response.redirect(req.header("Referer"));
});

// Via cookie (persistent)
Cookie.of("lang", lang)
    .maxAge(Duration.ofDays(365))
    .addTo(response);
Use nativeName() in language pickers - users recognize their language better.