add Localization and Sitemap sections (for v12.1.0+)

Gerasimos (Makis) Maropoulos 2019-12-13 21:08:58 +02:00
parent 06ac3b4352
commit e0d16ace31
No known key found for this signature in database
GPG Key ID: 5DBE766BD26A54E7
9 changed files with 1168 additions and 861 deletions

@ -40,6 +40,8 @@ This wiki is the main source of documentation for **developers** working with (o
* [[Websockets]]
* [[Dependency Injection|Dependency-Injection]]
* [[MVC]]
* [[Sitemap]]
* [[Localization]]
* [[Testing]]
* [➲ Examples](https://github.com/kataras/iris/tree/master/_examples)
* [[Starter Kits]]

@ -21,19 +21,19 @@ module your_project_name
go 1.13
require (
github.com/kataras/iris/v12 v12.0.1
github.com/kataras/iris/v12 v12.1.0
)
```
> `$ go build`
## How to update
<!-- ## How to update
Here is the go-get command to get the latest and greatest Iris version. Master branch is usually stable enough.
```bash
$ go get -u github.com/kataras/iris/v12@latest
```
$ go get -u github.com/kataras/iris/v12@master
``` -->
## Troubleshooting

283
Localization.md Normal file

@ -0,0 +1,283 @@
## Introduction
Localization features provide a convenient way to retrieve strings in various languages, allowing you to easily support multiple languages within your application. Language strings are stored in files within the `./locales` directory. Within this directory there should be a subdirectory for each language supported by the application:
```sh
│ main.go
└───locales
├───el-GR
│ home.yml
├───en-US
│ home.yml
└───zh-CN
home.yml
```
The default language for your application is the first registered language.
```go
app := iris.New()
// First parameter: Glob filpath patern,
// Second variadic parameter: Optional language tags,
// the first one is the default/fallback one.
app.I18n.Load("./locales/*/*", "en-US", "el-GR", "zh-CN")
```
Or if you load all languages by:
```go
app.I18n.Load("./locales/*/*")
```
Then set the default language using:
```go
app.I18n.SetDefault("en-US")
```
### Load embedded locales
You may want to embed locales with a go-bindata tool within your application executable.
1. install a go-bindata tool, e.g.
`$ go get -u github.com/go-bindata/go-bindata/...`
2. embed local files to your application
`$ go-bindata -o locales.go ./locales/...`
3. use the `LoadAssets` method to initialize and load the languages
^ The `AssetNames` and `Asset` functions are generated by `go-bindata`
```go
ap.I18n.LoadAssets(AssetNames, Asset, "en-US", "el-GR", "zh-CN")
```
## Defining Translations
Each file should contain keys with translated text or template values.
### Fmt Style
```yaml
hi: "Hi %s"
```
### Templates Style
```yaml
hi: "Hi {{ .Name }}"
# Template functions are supported too
# hi: "Hi {{sayHi .Name}}
```
### INI Sections
```ini
[cart]
checkout = ολοκλήρωση παραγγελίας - {{.Param}}
```
> YAML, TOML, JSON, INI files.
## Determining The Current Locale
You may use the `context.GetLocale` method to determine the current locale or check if the locale is a given value:
```go
func(ctx iris.Context) {
locale := ctx.GetLocale()
// [...]
}
```
The **Locale** interface looks like this.
```go
// Locale is the interface which returns from a `Localizer.GetLocale` metod.
// It serves the transltions based on "key" or format. See `GetMessage`.
type Locale interface {
// Index returns the current locale index from the languages list.
Index() int
// Tag returns the full language Tag attached tothis Locale,
// it should be uniue across different Locales.
Tag() *language.Tag
// Language should return the exact languagecode of this `Locale`
//that the user provided on `New` function.
//
// Same as `Tag().String()` but it's static.
Language() string
// GetMessage should return translated text based n the given "key".
GetMessage(key string, args ...interface{}) string
}
```
## Retrieving Translation
Use of `context.Tr` method as a shortcut to get a translated text for this request.
```go
func(ctx iris.Context) {
text := ctx.Tr("hi", "name")
// [...]
}
```
### INI Sections
INI Sections are separated by dot `"."`. The second optional value can be a `map` or a `struct` as the template value like the rest file formats.
```go
func(ctx iris.Context) {
text := ctx.Tr("cart.checkout", iris.Map{"Param": "a value"})
// [...]
}
```
## Inside Views
```go
func(ctx iris.Context) {
ctx.View("index.html", iris.Map{
"tr": ctx.Tr,
})
}
```
```html
{{ call .tr "hi" "John Doe" }}
```
## [Example](https://github.com/kataras/iris/tree/master/_examples/i18n)
```go
package main
import "github.com/kataras/iris/v12"
func newApp() *iris.Application {
app := iris.New()
// Configure i18n.
// First parameter: Glob filpath patern,
// Second variadic parameter: Optional language tags,
// the first one is the default/fallback one.
app.I18n.Load("./locales/*/*.ini", "en-US", "el-GR", "zh-CN")
// app.I18n.LoadAssets for go-bindata.
// Default values:
// app.I18n.URLParameter = "lang"
// app.I18n.Subdomain = true
//
// Set to false to disallow path (local) redirects,
// see https://github.com/kataras/iris/issues/1369.
// app.I18n.PathRedirect = true
app.Get("/", func(ctx iris.Context) {
hi := ctx.Tr("hi", "iris")
locale := ctx.GetLocale()
ctx.Writef("From the language %s translated output: %s", locale.Language(), hi)
})
app.Get("/some-path", func(ctx iris.Context) {
ctx.Writef("%s", ctx.Tr("hi", "iris"))
})
app.Get("/multi", func(ctx iris.Context) {
language := ctx.GetLocale().Language()
fromFirstFileValue := ctx.Tr("key1")
fromSecondFileValue := ctx.Tr("key2")
ctx.Writef("From the language: %s,
translated output:\n%s=%s\n%s=%s",
language, "key1", fromFirstFileValue,
"key2", fromSecondFileValue)
})
// using in inside your views:
view := iris.HTML("./views", ".html")
app.RegisterView(view)
app.Get("/templates", func(ctx iris.Context) {
ctx.View("index.html", iris.Map{
"tr": ctx.Tr, // word, arguments... {call .tr "hi" "iris"}}
})
// Note that,
// Iris automatically adds a "tr" global
// template function as well,
// the only differene is the way you call
// it inside your templates and
// that it accepts a language code as
// its first argument: {{ tr "el-GR" "hi" "iris"}}
})
//
return app
}
func main() {
app := newApp()
// go to http://localhost:8080/el-gr/some-path
// ^ (by path prefix)
//
// or http://el.mydomain.com8080/some-path
// ^ (by subdomain - test locally with the hosts file)
//
// or http://localhost:8080/zh-CN/templates
// ^ (by path prefix with uppercase)
//
// or http://localhost:8080/some-path?lang=el-GR
// ^ (by url parameter)
//
// or http://localhost:8080 (default is en-US)
// or http://localhost:8080/?lang=zh-CN
//
// go to http://localhost:8080/multi?lang=el-GR
// or http://localhost:8080/multi (default is en-US)
// or http://localhost:8080/multi?lang=en-US
//
// or use cookies to set the language.
app.Run(iris.Addr(":8080"), iris.WithSitemap("http://localhost:8080"))
}
```
## Sitemap
[[Sitemap]] translations are automatically set to each route by path prefix if `app.I18n.PathRedirect` is true or by subdomain if `app.I18n.Subdomain` is true or by URL query parameter if `app.I18n.URLParameter` is not empty.
Read more at: https://support.google.com/webmasters/answer/189077?hl=en
```sh
GET http://localhost:8080/sitemap.xml
```
```xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>http://localhost:8080/</loc>
<xhtml:link rel="alternate" hreflang="en-US" href="http://localhost:8080/"></xhtml:link>
<xhtml:link rel="alternate" hreflang="el-GR" href="http://localhost:8080/el-GR/"></xhtml:link>
<xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:8080/zh-CN/"></xhtml:link>
</url>
<url>
<loc>http://localhost:8080/some-path</loc>
<xhtml:link rel="alternate" hreflang="en-US" href="http://localhost:8080/some-path"></xhtml:link>
<xhtml:link rel="alternate" hreflang="el-GR" href="http://localhost:8080/el-GR/some-path"></xhtml:link>
<xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:8080/zh-CN/some-path"></xhtml:link>
</url>
<url>
<loc>http://localhost:8080/multi</loc>
<xhtml:link rel="alternate" hreflang="en-US" href="http://localhost:8080/multi"></xhtml:link>
<xhtml:link rel="alternate" hreflang="el-GR" href="http://localhost:8080/el-GR/multi"></xhtml:link>
<xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:8080/zh-CN/multi"></xhtml:link>
</url>
<url>
<loc>http://localhost:8080/templates</loc>
<xhtml:link rel="alternate" hreflang="en-US" href="http://localhost:8080/templates"></xhtml:link>
<xhtml:link rel="alternate" hreflang="el-GR" href="http://localhost:8080/el-GR/templates"></xhtml:link>
<xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:8080/zh-CN/templates"></xhtml:link>
</url>
</urlset>
```

@ -13,7 +13,6 @@ Prepare a cup of coffee or tea, whatever pleases you the most, and read some art
* [Iris Go Framework + MongoDB](https://bit.ly/2WDOsZF)
* [How to build a file upload form using DropzoneJS and Go](https://bit.ly/2ygMMqn)
* [How to display existing files on server using DropzoneJS and Go](https://bit.ly/2yjrckQ)
* [Iris, a modular web framework](https://bit.ly/2KHm6q0)
* [Go vs .NET Core in terms of HTTP performance](https://bit.ly/2Kh7ezl)
* [Iris Go vs .NET Core Kestrel in terms of HTTP performance](https://bit.ly/2yo2v6J)
* [How to Turn an Android Device into a Web Server](https://bit.ly/2Icl5EM)

@ -3,72 +3,7 @@
Here is a full list of methods that the `iris.Context` provides.
```go
type (
// BodyDecoder is an interface which any struct can implement in order to customize the decode action
// from ReadJSON and ReadXML
//
// Trivial example of this could be:
// type User struct { Username string }
//
// func (u *User) Decode(data []byte) error {
// return json.Unmarshal(data, u)
// }
//
// the 'context.ReadJSON/ReadXML(&User{})' will call the User's
// Decode option to decode the request body
//
// Note: This is totally optionally, the default decoders
// for ReadJSON is the encoding/json and for ReadXML is the encoding/xml.
BodyDecoder interface {
Decode(data []byte) error
}
// Unmarshaler is the interface implemented by types that can unmarshal any raw data.
// TIP INFO: Any pointer to a value which implements the BodyDecoder can be override the unmarshaler.
Unmarshaler interface {
Unmarshal(data []byte, outPtr interface{}) error
}
// UnmarshalerFunc a shortcut for the Unmarshaler interface
//
// See 'Unmarshaler' and 'BodyDecoder' for more.
UnmarshalerFunc func(data []byte, outPtr interface{}) error
)
// Unmarshal parses the X-encoded data and stores the result in the value pointed to by v.
// Unmarshal uses the inverse of the encodings that Marshal uses, allocating maps,
// slices, and pointers as necessary.
func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error {
return u(data, v)
}
// Context is the midle-man server's "object" for the clients.
//
// A New context is being acquired from a sync.Pool on each connection.
// The Context is the most important thing on the iris's http flow.
//
// Developers send responses to the client's request through a Context.
// Developers get request information from the client's request a Context.
type Context interface {
// BeginRequest is executing once for each request
// it should prepare the (new or acquired from pool) context's fields for the new request.
//
// To follow the iris' flow, developer should:
// 1. reset handlers to nil
// 2. reset values to empty
// 3. reset sessions to nil
// 4. reset response writer to the http.ResponseWriter
// 5. reset request to the *http.Request
// and any other optional steps, depends on dev's application type.
BeginRequest(http.ResponseWriter, *http.Request)
// EndRequest is executing once after a response to the request was sent and this context is useless or released.
//
// To follow the iris' flow, developer should:
// 1. flush the response writer's result
// 2. release the response writer
// and any other optional steps, depends on dev's application type.
EndRequest()
// ResponseWriter returns an http.ResponseWriter compatible response writer, as expected.
ResponseWriter() ResponseWriter
// ResetResponseWriter should change or upgrade the Context's ResponseWriter.
@ -180,7 +115,7 @@ type Context interface {
// Next calls all the next handler from the handlers chain,
// it should be used inside a middleware.
//
// Note: Custom context should override this method in order to be able to pass its own iris.Context implementation.
// Note: Custom context should override this method in order to be able to pass its own context.Context implementation.
Next()
// NextOr checks if chain has a next handler, if so then it executes it
// otherwise it sets a new chain assigned to this Context based on the given handler(s)
@ -254,15 +189,9 @@ type Context interface {
// You can use this function to Set and Get local values
// that can be used to share information between handlers and middleware.
Values() *memstore.Store
// Translate is the i18n (localization) middleware's function,
// it calls the Values().Get(ctx.Application().ConfigurationReadOnly().GetTranslateFunctionContextKey())
// to execute the translate function and return the localized text value.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n
Translate(format string, args ...interface{}) string
// +------------------------------------------------------------+
// | Path, Host, Subdomain, IP, Headers etc... |
// | Path, Host, Subdomain, IP, Headers, Localization etc... |
// +------------------------------------------------------------+
// Method returns the request.Method, the client's http method to the server.
@ -321,6 +250,14 @@ type Context interface {
// in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
// or by the URL query parameter "referer".
GetReferrer() Referrer
// GetLocale returns the current request's `Locale` found by i18n middleware.
// See `Tr` too.
GetLocale() Locale
// Tr returns a i18n localized message based on format with optional arguments.
// See `GetLocale` too.
// Example: https://github.com/kataras/iris/tree/master/_examples/i18n
Tr(format string, args ...interface{}) string
// +------------------------------------------------------------+
// | Headers helpers |
// +------------------------------------------------------------+
@ -347,6 +284,8 @@ type Context interface {
// Look `StatusCode` too.
GetStatusCode() int
// AbsoluteURI parses the "s" and returns its absolute URI form.
AbsoluteURI(s string) string
// Redirect sends a redirect response to the client
// to a specific url or relative path.
// accepts 2 parameters string and an optional int
@ -357,7 +296,6 @@ type Context interface {
// or 303 (StatusSeeOther) if POST method,
// or StatusTemporaryRedirect(307) if that's nessecery.
Redirect(urlToRedirect string, statusHeader ...int)
// +------------------------------------------------------------+
// | Various Request and Post Data |
// +------------------------------------------------------------+
@ -371,7 +309,7 @@ type Context interface {
URLParam(name string) string
// URLParamTrim returns the url query parameter with trailing white spaces removed from a request.
URLParamTrim(name string) string
// URLParamTrim returns the escaped url query parameter from a request.
// URLParamEscape returns the escaped url query parameter from a request.
URLParamEscape(name string) string
// URLParamInt returns the url query parameter as int value from a request,
// returns -1 and an error if parse failed.
@ -514,7 +452,7 @@ type Context interface {
// to be executed. Next handlers are being executed on iris because you can alt the
// error code and change it to a more specific one, i.e
// users := app.Party("/users")
// users.Done(func(ctx iris.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }})
// users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }})
NotFound()
// +------------------------------------------------------------+
@ -548,6 +486,10 @@ type Context interface {
//
// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go
ReadXML(xmlObjectPtr interface{}) error
// ReadYAML reads YAML from request's body and binds it to the "outPtr" value.
//
// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-yaml/main.go
ReadYAML(outPtr interface{}) error
// ReadForm binds the formObject with the form data
// it supports any kind of type, including custom structs.
// It will return nothing if request data are empty.
@ -613,7 +555,7 @@ type Context interface {
// like the HTTP Method is not "GET" or "HEAD" or if the "modtime" is zero
// or if parsing time from the header failed.
//
// It's mostly used internally, e.g. `context#WriteWithExpiration`.
// It's mostly used internally, e.g. `context#WriteWithExpiration`. See `ErrPreconditionFailed` too.
//
// Note that modtime.UTC() is being used instead of just modtime, so
// you don't have to know the internals in order to make that works.
@ -738,11 +680,67 @@ type Context interface {
// JSONP marshals the given interface object and writes the JSON response.
JSONP(v interface{}, options ...JSONP) (int, error)
// XML marshals the given interface object and writes the XML response.
// To render maps as XML see the `XMLMap` package-level function.
XML(v interface{}, options ...XML) (int, error)
// Problem writes a JSON or XML problem response.
// Order of Problem fields are not always rendered the same.
//
// Behaves exactly like `Context.JSON`
// but with default ProblemOptions.JSON indent of " " and
// a response content type of "application/problem+json" instead.
//
// Use the options.RenderXML and XML fields to change this behavior and
// send a response of content type "application/problem+xml" instead.
//
// Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers
Problem(v interface{}, opts ...ProblemOptions) (int, error)
// Markdown parses the markdown to html and renders its result to the client.
Markdown(markdownB []byte, options ...Markdown) (int, error)
// YAML parses the "v" using the yaml parser and renders its result to the client.
YAML(v interface{}) (int, error)
// +-----------------------------------------------------------------------+
// | Content Νegotiation |
// | https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation | |
// +-----------------------------------------------------------------------+
// Negotiation creates once and returns the negotiation builder
// to build server-side available content for specific mime type(s)
// and charset(s).
//
// See `Negotiate` method too.
Negotiation() *NegotiationBuilder
// Negotiate used for serving different representations of a resource at the same URI.
//
// The "v" can be a single `N` struct value.
// The "v" can be any value completes the `ContentSelector` interface.
// The "v" can be any value completes the `ContentNegotiator` interface.
// The "v" can be any value of struct(JSON, JSONP, XML, YAML) or
// string(TEXT, HTML) or []byte(Markdown, Binary) or []byte with any matched mime type.
//
// If the "v" is nil, the `Context.Negotitation()` builder's
// content will be used instead, otherwise "v" overrides builder's content
// (server mime types are still retrieved by its registered, supported, mime list)
//
// Set mime type priorities by `Negotiation().JSON().XML().HTML()...`.
// Set charset priorities by `Negotiation().Charset(...)`.
// Set encoding algorithm priorities by `Negotiation().Encoding(...)`.
// Modify the accepted by
// `Negotiation().Accept./Override()/.XML().JSON().Charset(...).Encoding(...)...`.
//
// It returns `ErrContentNotSupported` when not matched mime type(s).
//
// Resources:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
//
// Supports the above without quality values.
//
// Read more at: https://github.com/kataras/iris/wiki/Content-negotiation
Negotiate(v interface{}) (int, error)
// +------------------------------------------------------------+
// | Serve files |
// +------------------------------------------------------------+

@ -136,7 +136,6 @@ Here is a list of some handlers made specifically for Iris:
| Middleware | Example |
| -----------|-------------|
| [basic authentication](https://github.com/kataras/iris/tree/master/middleware/basicauth) | [iris/_examples/authentication/basicauth](https://github.com/kataras/iris/tree/master/_examples/authentication/basicauth) |
| [localization and internationalization](https://github.com/kataras/iris/tree/master/middleware/i18n) | [iris/_examples/miscellaneous/i81n](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n) |
| [request logger](https://github.com/kataras/iris/tree/master/middleware/logger) | [iris/_examples/http_request/request-logger](https://github.com/kataras/iris/tree/master/_examples/http_request/request-logger) |
| [HTTP method override](https://github.com/kataras/iris/tree/master/middleware/methodoverride) | [iris/middleware/methodoverride/methodoverride_test.go](https://github.com/kataras/iris/blob/master/middleware/methodoverride/methodoverride_test.go) |
| [profiling (pprof)](https://github.com/kataras/iris/tree/master/middleware/pprof) | [iris/_examples/miscellaneous/pprof](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/pprof) |

24
Sitemap.md Normal file

@ -0,0 +1,24 @@
Iris provides extensive support for the Sitemap Protocol which automatically generates [sitemap index](https://www.sitemaps.org/protocol.html#index) "/sitemap.xml" too.
To enable sitemaps on your web application you should use the `iris.WithSitemap` Configurator. This function accepts the full scheme and domain of the exposed application.
```go
app := iris.New()
// [...]
app.Run(iris.Addr(":8080"), iris.WithSitemap("http://localhost:8080"))
```
The application will loop through registered _static_ routes and it will add each one of them to the sitemap file. By default only the `<loc>` XML element will be filled unless the route's fields `LastMod`, `ChangeFreq` or/and `Priority`[*](https://www.sitemaps.org/protocol.html) are set.
```go
app.Get("/home", handler).SetLastMod(time.Now()).SetChangeFreq("hourly").SetPriority(0.8)
```
> A static route is exposed on GET HTTP Method and its path does not contain a dynamic parameter. e.g. /home, /about but not /user/{id:uint64} and e.t.c.
See it in action, download and run the **example** from: https://github.com/kataras/iris/tree/master/_examples/sitemap
If the application **is localized** then `iris.WithSitemap` will add `xhtml:link` XML elements to the sitemap file for each translation language as recommended at: https://support.google.com/webmasters/answer/189077?hl=en. Read more about it at the [[Localization]] section.

@ -9,7 +9,7 @@ Let's see the list of the built-in view engines:
| Engine | Declaration | Underline Template Parser
| -----------|-------------|-------------|
| std template/html | `iris.HTML(...)` | [html/template](https://golang.org/pkg/html/template/) package |
| django | `iris.Django(...)` | [flosch/pongo2](https://github.com/flosch/pongo2) package |
| django | `iris.Django(...)` | [iris-contrib/pongo2](https://github.com/iris-contrib/pongo2) package |
| handlebars | `iris.Handlebars(...)` | [Joker/jade](https://github.com/Joker/jade) package |
| amber | `iris.Amber(...)` | [aymerick/raymond](https://github.com/aymerick/raymond) package |
| pug(jade) | `iris.Pug(...)` | [eknkc/amber](https://github.com/eknkc/amber) package |

@ -35,6 +35,8 @@
* [[Websockets]]
* [[Dependency Injection|Dependency-Injection]]
* [[MVC]]
* [[Sitemap]]
* [[Localization]]
* [[Testing]]
* [Examples](https://github.com/kataras/iris/tree/master/_examples)
* [[Starter Kits]]