Skip to content
Open
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
203 changes: 203 additions & 0 deletions Documentation/HowTo/generate-multi-language-site.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# Generate Multi-Language Site

This post talks about how to produce multi-language site using Publish.
For example, `https://example.com/en/post/title/` and `https://example.com/zh/post/title/` for two language versions of the same post, where `en` and `zh` specifies language.

This feature did not come with standard `Website` struct of Publish. It needs some tweaks to make this possible.

## Basic Usage

### Generate Publish Project

Generate a new Publish project if you haven't got one. Check introduction in official readme. [https://github.com/JohnSundell/Publish](https://github.com/JohnSundell/Publish)

If you already have a working Publish project, you may skip this step and modify the existing one.

### Setup Your Website

- Modify your website to conform to `MultiLanguageWebsite` instead of `Website`.
- Add a new variable `var languages: [Language]`, an array of languages your website has.
- Modify the `ItemMetadata` to conform to `MultiLanguageWebsiteItemMetadata` instead of `WebsiteItemMetadata`.
- Add a variable in `ItemMetadata`: `var alternateLinkIdentifier: String?`.
- *Optional* Remove the `var language: Language` variable, or change it to default language of your website.

A modified version of the website that supports English and Chinese would look like below:

```swift
// This type acts as the configuration for your website.
struct InternationalWebsite: MultiLanguageWebsite {
// The languages of your website. The first language becomes default language.
var languages: [Language] = [.english, .chinese, .japanese]

enum SectionID: String, WebsiteSectionID {
// Add the sections that you want your website to contain here:
case posts
}

struct ItemMetadata: MultiLanguageWebsiteItemMetadata {
// A metadata entry to correlate posts in different languages.
var alternateLinkIdentifier: String?

// Add any site-specific metadata that you want to use here.
}

// Update these properties to configure your website:
var url = URL(string: "https://your-website-url.com")!
var name = "InternationalWebsite
var description = "A description of InternationalWebsite"
var imagePath: Path? { nil }
}
```

### Arrange Markdown Files

By default, the markdown files for posts are located in 'Content' folder, with the following structure:

```
Content/
SectionOne/
PostOne.md
PostTwo.md
SectionTwo/
PostThree.md
```

Change the above structure and make a folder for each language, where the folder name is language code of the language.
You may find a list of supported languages in `Language.swift` of Plot.

```
en/
SectionOne/
PostOne.md
PostTwo.md
SectionTwo/
PostThree.md

zh/
SectionOne/
PostOne.md
PostTwo.md
SectionTwo/
PostThree.md

ja/
SectionOne/
PostOne.md
PostTwo.md
SectionTwo/
PostThree.md
```

### Localize Theme

Localize the Theme you are using to show different versions for languages.

For example, the `makeIndexHTML()` becomes

```swift
func makeIndexHTML(for index: Index,
context: PublishingContext<Site>) throws -> HTML {
HTML(
.lang(index.language!), // Use language of the index.
.head(for: index, on: context.site),
.body(
.header(for: context, selectedSection: nil),
.wrapper(
.h1(.text(index.title)),
.p(
.class("description"),
.text(context.site.description)
),
.h2("Latest content"),
.itemList(
for: context.allItems(
sortedBy: \.date,
in: index.language!, // Get items in the specific language only.
order: .descending
),
on: context.site
)
),
.footer(for: context.site)
)
)
}
```

You may need to localize the text of the website, *e.g.* section names, links, etc.

It is the time to use your creativity to make your own website localized.

## More Usage

### Customize the name of content folder name of each language.

By default, Publish process markdown files of each language located in folder named by language code, *e.g.* en, zh, etc.
To change it, implement the following method of your website:

```
extension InternationalWebsite {
func contentFolder(for language: Language) -> String {
switch language {
case .english:
return "English Content" // "en" by default
case .chinese:
return "Chinese Content" // "zh" by default
case .japanese:
return "Japanese Content" // "ja" by default
default:
return language.rawValue
}
}
}
```

### Customize the language component of output path.

By default, Publish outputs html files of each language located in folder named by language code, *e.g.* en, zh, etc.
To change it, implement the following method of your website:

```
extension InternationalWebsite {
func pathPrefix(for language: Language) -> String {
switch language {
case .english:
return "us" // "en" by default
case .chinese:
return "cn" // "zh" by default
case .japanese:
return "jp" // "ja" by default
default:
return language.rawValue
}
}
}
```

### Specify the language of markdown file.

You may specify then language of markdown file by setting `language` to language code in metadata.

```
---
language: en
---
```

### Correlate markdown files in different languages

By default, markdown files with the same path are considered as language variations of the same item.
If you want to correlate markdown files with different file name, e.g.

- en/Section/example.md in English
- zh/Section/样例.md in Chinese
- ja/Section/例.md in Japanese

You need to correlate them by setting `alternateLinkIdentifier` to the same value in metadata of the above files.

```
---
alternateLinkIdentifier: example-markdown
---
```

1 change: 1 addition & 0 deletions Documentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ Shorter articles focused on explaining how to get a given task done using Publis
- [Expressing custom metadata values using Markdown](HowTo/custom-markdown-metadata-values.md)
- [Nesting items within folders](HowTo/nested-items.md)
- [Using a custom `DateFormatter`](HowTo/using-a-custom-date-formatter.md)
- [Generate Multi-Language Site](HowTo/generate-multi-language-site.md)

*Contributions adding more “How to” articles, or other kinds of documentation, are more than welcome.*
4 changes: 4 additions & 0 deletions Sources/Publish/API/Content.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ public struct Content: Hashable, ContentProtocol {
public var imagePath: Path?
public var audio: Audio?
public var video: Video?
public var language: Language?

/// Initialize a new instance of this type
/// - parameter title: The location's title.
/// - parameter description: A description of the location.
/// - parameter body: The main body of the location's content.
/// - parameter date: The location's main publishing date.
/// - parameter lastModified: The last modification date.
/// - parameter language: The language of the content.
/// - parameter imagePath: A path to any image for the location.
/// - parameter audio: Any audio data associated with this content.
/// - parameter video: Any video data associated with this content.
Expand All @@ -32,6 +34,7 @@ public struct Content: Hashable, ContentProtocol {
body: Body = Body(html: ""),
date: Date = Date(),
lastModified: Date = Date(),
language: Language? = nil,
imagePath: Path? = nil,
audio: Audio? = nil,
video: Video? = nil) {
Expand All @@ -40,6 +43,7 @@ public struct Content: Hashable, ContentProtocol {
self.body = body
self.date = date
self.lastModified = lastModified
self.language = language
self.imagePath = imagePath
self.audio = audio
self.video = video
Expand Down
5 changes: 5 additions & 0 deletions Sources/Publish/API/ContentProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import Foundation
import Plot

/// Protocol adopted by types that represent the content for a location.
public protocol ContentProtocol {
Expand Down Expand Up @@ -43,4 +44,8 @@ public protocol ContentProtocol {
/// can be used to display inline video players using the `videoPlayer`
/// Plot component. See `Video` for more info.
var video: Video? { get set }
/// The language of the location's content, which
/// can be used to specify the language using the `lang`
/// Plot component. See `Language` for more info.
var language: Language? { get set }
}
6 changes: 6 additions & 0 deletions Sources/Publish/API/Location.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import Foundation
import Plot

/// Protocol adopted by types that can act as a location
/// that a user can navigate to within a web browser.
Expand Down Expand Up @@ -62,4 +63,9 @@ public extension Location {
get { content.video }
set { content.video = newValue }
}

var language: Language? {
get { content.language }
set { content.language = newValue }
}
}
Loading