Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ExampleI18n/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
deploy
25 changes: 25 additions & 0 deletions ExampleI18n/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// swift-tools-version:6.0

import PackageDescription

let package = Package(
name: "ExampleI18n",
platforms: [
.macOS(.v14),
],
dependencies: [
.package(path: "../"),
.package(url: "https://github.com/loopwerk/SagaParsleyMarkdownReader", from: "1.0.0"),
.package(url: "https://github.com/loopwerk/SagaSwimRenderer", from: "1.0.0"),
],
targets: [
.executableTarget(
name: "ExampleI18n",
dependencies: [
"Saga",
"SagaParsleyMarkdownReader",
"SagaSwimRenderer",
]
),
]
)
42 changes: 42 additions & 0 deletions ExampleI18n/Sources/ExampleI18n/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Foundation
import Saga
import SagaParsleyMarkdownReader
import SagaPathKit
import SagaSwimRenderer

enum SiteMetadata {
static let url = URL(string: "http://www.example.com")!
static let name = "i18n Example"
static let author = "Kevin Renskers"
}

struct ArticleMetadata: Metadata {
let tags: [String]
}

try await Saga(input: "content", output: "deploy")
.i18n(locales: ["en", "nl"], defaultLocale: "en")

// Articles with list and tag pages
.register(
folder: "articles",
metadata: ArticleMetadata.self,
readers: [.parsleyMarkdownReader],
writers: [
.itemWriter(swim(renderArticle)),
.listWriter(swim(renderArticles)),
.tagWriter(swim(renderTag), tags: \.metadata.tags),
]
)

// All remaining markdown files (index, about)
.register(
metadata: EmptyMetadata.self,
readers: [.parsleyMarkdownReader],
writers: [.itemWriter(swim(renderPage))]
)

// Sitemap
.createPage("sitemap.xml", using: Saga.sitemap(baseURL: SiteMetadata.url))

.run()
156 changes: 156 additions & 0 deletions ExampleI18n/Sources/ExampleI18n/templates.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import Foundation
import HTML
import Saga
import SagaPathKit
import SagaSwimRenderer

// MARK: - Translations

func t(_ key: String, locale: String) -> String {
let strings: [String: [String: String]] = [
"en": [
"articles": "Articles",
"about": "About",
"home": "Home",
"tagged": "Tagged",
"read_more": "Read more",
"built_with": "Built with",
],
"nl": [
"articles": "Artikelen",
"about": "Over ons",
"home": "Home",
"tagged": "Getagd",
"read_more": "Lees meer",
"built_with": "Gebouwd met",
],
]
return strings[locale]?[key] ?? key
}

// MARK: - Language switcher

func languageSwitcher(currentLocale: String, item: AnyItem) -> Node {
nav(class: "lang-switcher") {
if currentLocale == "en" {
span(class: "active") { "EN" }
} else {
if let enVersion = item.translations["en"] {
a(href: enVersion.url) { "EN" }
}
}

if currentLocale == "nl" {
span(class: "active") { "NL" }
} else {
if let nlVersion = item.translations["nl"] {
a(href: nlVersion.url) { "NL" }
}
}
}
}

// MARK: - Base layout

func baseHtml(title pageTitle: String, locale: String, @NodeBuilder children: () -> NodeConvertible) -> Node {
let articlesPath = locale == "en" ? "/articles/" : "/nl/articles/"
let aboutPath = locale == "en" ? "/about/" : "/nl/over-ons/"
let homePath = locale == "en" ? "/" : "/nl/"

return html(lang: locale) {
head {
meta(charset: "utf-8")
meta(content: "width=device-width, initial-scale=1", name: "viewport")
title { SiteMetadata.name + ": " + pageTitle }
link(href: "/static/style.css", rel: "stylesheet")
}
body {
header {
nav {
a(class: "site-title", href: homePath) { SiteMetadata.name }
div(class: "nav-links") {
a(href: homePath) { t("home", locale: locale) }
a(href: articlesPath) { t("articles", locale: locale) }
a(href: aboutPath) { t("about", locale: locale) }
}
}
}
main {
children()
}
footer {
p {
t("built_with", locale: locale)
" "
a(href: "https://github.com/loopwerk/Saga") { "Saga" }
}
}
}
}
}

// MARK: - Articles

func renderArticle(context: ItemRenderingContext<ArticleMetadata>) -> Node {
let locale = context.locale ?? "en"
let tagBase = locale == "en" ? "/articles/tag" : "/nl/articles/tag"

return baseHtml(title: context.item.title, locale: locale) {
languageSwitcher(currentLocale: locale, item: context.item)

h1 { context.item.title }
ul(class: "tags") {
context.item.metadata.tags.map { tag in
li {
a(href: "\(tagBase)/\(tag.slugified)/") { tag }
}
}
}
div(class: "article-body") {
Node.raw(context.item.body)
}
}
}

func renderArticles(context: ItemsRenderingContext<ArticleMetadata>) -> Node {
let locale = context.locale ?? "en"

return baseHtml(title: t("articles", locale: locale), locale: locale) {
h1 { t("articles", locale: locale) }
context.items.map { article in
div(class: "article-card") {
a(href: article.url) { article.title }
}
}
}
}

func renderTag(context: PartitionedRenderingContext<String, ArticleMetadata>) -> Node {
let locale = context.locale ?? "en"

return baseHtml(title: "\(t("tagged", locale: locale)): \(context.key)", locale: locale) {
h1 { "\(t("tagged", locale: locale)): \(context.key)" }
context.items.map { article in
div(class: "article-card") {
a(href: article.url) { article.title }
}
}
}
}

// MARK: - Generic pages

func renderPage(context: ItemRenderingContext<EmptyMetadata>) -> Node {
let locale = context.locale ?? "en"

return baseHtml(title: context.item.title, locale: locale) {
languageSwitcher(currentLocale: locale, item: context.item)

div(class: "page") {
h1 { context.item.title }
div(class: "article-body") {
Node.raw(context.item.body)
}
}
}
}
5 changes: 5 additions & 0 deletions ExampleI18n/content/en/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
slug: about
---
# About this site
This is an example site demonstrating Saga's internationalization support. Content is organized in locale folders (`en/`, `nl/`) and Saga handles translation linking and per-locale output automatically.
15 changes: 15 additions & 0 deletions ExampleI18n/content/en/articles/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
tags: tutorial
date: 2024-07-01
---
# Getting Started with Saga
To create a multilingual site with Saga, start by organizing your content into locale folders. Then configure i18n before your register calls:

```swift
try await Saga(input: "content", output: "deploy")
.i18n(locales: ["en", "nl"], defaultLocale: "en")
.register(...)
.run()
```

Your writers automatically run per-locale, so list pages, tag pages, and feeds are all generated separately for each language.
8 changes: 8 additions & 0 deletions ExampleI18n/content/en/articles/hello-world.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
tags: news, welcome
date: 2024-06-15
---
# Hello World
Welcome to our multilingual blog! This is our very first article, available in both English and Dutch.

Saga makes it easy to build sites that serve content in multiple languages. Each locale gets its own folder, and translations are linked automatically by matching filenames.
4 changes: 4 additions & 0 deletions ExampleI18n/content/en/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Welcome
This is an example multilingual site built with Saga. It supports English and Dutch.

Check out the [articles](/articles/) or read more [about this site](/about/).
5 changes: 5 additions & 0 deletions ExampleI18n/content/nl/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
slug: over-ons
---
# Over deze site
Dit is een voorbeeldsite die de internationalisatie-ondersteuning van Saga demonstreert. Content is georganiseerd in taalspecifieke mappen (`en/`, `nl/`) en Saga koppelt vertalingen automatisch aan elkaar.
15 changes: 15 additions & 0 deletions ExampleI18n/content/nl/articles/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
tags: handleiding
date: 2024-07-01
---
# Aan de slag met Saga
Om een meertalige site te maken met Saga, begin je met het organiseren van je content in taalmappen. Configureer daarna i18n voor je register-aanroepen:

```swift
try await Saga(input: "content", output: "deploy")
.i18n(locales: ["en", "nl"], defaultLocale: "en")
.register(...)
.run()
```

Je writers draaien automatisch per taal, dus lijstpagina's, tagpagina's en feeds worden allemaal apart gegenereerd voor elke taal.
8 changes: 8 additions & 0 deletions ExampleI18n/content/nl/articles/hello-world.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
tags: nieuws, welkom
date: 2024-06-15
---
# Hallo Wereld
Welkom op onze meertalige blog! Dit is ons allereerste artikel, beschikbaar in zowel Engels als Nederlands.

Saga maakt het eenvoudig om sites te bouwen die content in meerdere talen aanbieden. Elke taal krijgt zijn eigen map, en vertalingen worden automatisch gekoppeld op basis van bestandsnamen.
4 changes: 4 additions & 0 deletions ExampleI18n/content/nl/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Welkom
Dit is een voorbeeld meertalige site gebouwd met Saga. De site ondersteunt Engels en Nederlands.

Bekijk de [artikelen](/nl/articles/) of lees meer [over deze site](/nl/over-ons/).
Loading
Loading