refactor i18n support
This commit is contained in:
parent
da955f2c48
commit
61205ced5f
7 changed files with 206 additions and 140 deletions
|
|
@ -7,15 +7,11 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/fatih/color"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
var slotRe = regexp.MustCompile(`\[\{([^\}]+)\}\]`)
|
||||
var cssRe = regexp.MustCompile(`([\.a-z0-9A-Z-:# ]+)\{`)
|
||||
var langFuncRe = regexp.MustCompile(`L\s*\(\s*["'](.*?)["']\s*\)`)
|
||||
var langAttrRe = regexp.MustCompile(`'::(.*?)'`)
|
||||
|
||||
// Build build the page
|
||||
func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Document, []string, error) {
|
||||
|
|
@ -500,104 +496,3 @@ func addTabToEachLine(input string, prefix ...string) string {
|
|||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func getScriptTranslation(code string, namespace string) []Translation {
|
||||
translations := []Translation{}
|
||||
matches := langFuncRe.FindAllStringSubmatch(code, -1)
|
||||
for i, match := range matches {
|
||||
translations = append(translations, Translation{
|
||||
Key: fmt.Sprintf("%s_script_%d", namespace, i),
|
||||
Message: match[1],
|
||||
Type: "script",
|
||||
})
|
||||
}
|
||||
return translations
|
||||
}
|
||||
|
||||
func getNodeTranslation(sel *goquery.Selection, index int, namespace string) []Translation {
|
||||
|
||||
translations := []Translation{}
|
||||
nodeType := sel.Get(0).Type
|
||||
switch nodeType {
|
||||
case html.ElementNode:
|
||||
|
||||
// Get the translation
|
||||
if typ, has := sel.Attr("s:trans"); has {
|
||||
typ = strings.TrimSpace(typ)
|
||||
if typ == "" {
|
||||
typ = "html"
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s_index_%d", namespace, index)
|
||||
translations = append(translations, Translation{
|
||||
Key: key,
|
||||
Message: strings.TrimSpace(sel.Text()),
|
||||
Type: typ,
|
||||
})
|
||||
sel.SetAttr("s:trans-node", key)
|
||||
sel.RemoveAttr("s:trans")
|
||||
}
|
||||
|
||||
// Attributes
|
||||
keys := map[string][]string{}
|
||||
has := false
|
||||
for i, attr := range sel.Get(0).Attr {
|
||||
|
||||
keys[attr.Key] = []string{}
|
||||
|
||||
// value="::attr"
|
||||
if strings.HasPrefix(attr.Val, "::") {
|
||||
key := fmt.Sprintf("%s_index_attr_%d_%d", namespace, index, i)
|
||||
translations = append(translations, Translation{
|
||||
Key: fmt.Sprintf("%s_index_attr_%d_%d", namespace, index, i),
|
||||
Message: attr.Val[2:],
|
||||
Name: attr.Key,
|
||||
Type: "attr",
|
||||
})
|
||||
keys[attr.Key] = append(keys[attr.Key], key)
|
||||
has = true
|
||||
}
|
||||
|
||||
// value="{{ 'key': '::value' }}"
|
||||
matches := langAttrRe.FindAllStringSubmatch(attr.Val, -1)
|
||||
if len(matches) > 0 {
|
||||
for j, match := range matches {
|
||||
key := fmt.Sprintf("%s_index_attr_%d_%d_%d", namespace, index, i, j)
|
||||
translations = append(translations, Translation{
|
||||
Key: fmt.Sprintf("%s_index_attr_%d_%d_%d", namespace, index, i, j),
|
||||
Message: match[1],
|
||||
Name: attr.Key,
|
||||
Type: "attr",
|
||||
})
|
||||
keys[attr.Key] = append(keys[attr.Key], key)
|
||||
has = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if has {
|
||||
raw, err := jsoniter.Marshal(keys)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString(err.Error()))
|
||||
break
|
||||
}
|
||||
sel.SetAttr("s:trans-attrs", string(raw))
|
||||
sel.RemoveAttr("s:trans")
|
||||
}
|
||||
|
||||
case html.TextNode:
|
||||
if strings.HasPrefix(sel.Text(), "::") {
|
||||
key := fmt.Sprintf("%s_index_%d", namespace, index)
|
||||
translations = append(translations, Translation{
|
||||
Key: fmt.Sprintf("%s_index_%d", namespace, index),
|
||||
Message: strings.TrimSpace(sel.Text()[2:]),
|
||||
Type: "text",
|
||||
})
|
||||
sel.SetAttr("s:trans-node", key)
|
||||
sel.RemoveAttr("s:trans")
|
||||
}
|
||||
}
|
||||
|
||||
return translations
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,20 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou/runtime/transform"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
var quoteRe = "'\"`"
|
||||
var importRe = regexp.MustCompile(`import\s*\t*\n*[^;]*;`) // import { foo, bar } from 'hello'; ...
|
||||
var importAssetsRe = regexp.MustCompile(`import\s*\t*\n*\s*['"]@assets\/([^'"]+)['"];`) // import '@assets/foo.js'; or import "@assets/foo.js";
|
||||
var transStmtReSingle = regexp.MustCompile(`'::([^:']+)'`)
|
||||
var transStmtReDouble = regexp.MustCompile(`"::([^:"]+)"`)
|
||||
var transFuncRe = regexp.MustCompile(`__m\s*\(\s*["'](.*?)["']\s*\)`)
|
||||
|
||||
// AssetsRe is the regexp for assets
|
||||
var AssetsRe = regexp.MustCompile(`[` + quoteRe + `]@assets\/([^` + quoteRe + `]+)[` + quoteRe + `]`) // '@assets/foo.js' or "@assets/foo.js" or `@assets/foo`
|
||||
|
|
@ -85,6 +90,13 @@ func (page *Page) Compile(ctx *BuildContext, option *BuildOption) (string, []str
|
|||
)
|
||||
}
|
||||
|
||||
// Add the translation marks
|
||||
sequence := 0
|
||||
err = page.TranslateMarks(ctx, option, doc, &sequence)
|
||||
if err != nil {
|
||||
return "", warnings, err
|
||||
}
|
||||
|
||||
page.ReplaceDocument(doc)
|
||||
html, err := doc.Html()
|
||||
if err != nil {
|
||||
|
|
@ -140,6 +152,13 @@ func (page *Page) CompileAsComponent(ctx *BuildContext, option *BuildOption) (st
|
|||
body.Children().First().AppendHtml(fmt.Sprintf(`<script name="styles" type="json">%s</script>`+"\n", rawStyles))
|
||||
body.Children().First().AppendHtml(fmt.Sprintf(`<script name="option" type="json">%s</script>`+"\n", rawOption))
|
||||
|
||||
// Add the translation marks
|
||||
sequence := 0
|
||||
err = page.TranslateMarks(ctx, option, doc, &sequence)
|
||||
if err != nil {
|
||||
return "", warnings, err
|
||||
}
|
||||
|
||||
html, err := body.Html()
|
||||
return html, warnings, err
|
||||
}
|
||||
|
|
@ -263,3 +282,146 @@ func (style StyleNode) HTML() string {
|
|||
return "<style " + strings.Join(attrs, " ") + ">\n" + style.Source + "\n</style>"
|
||||
|
||||
}
|
||||
|
||||
// TranslateMarks add the translation marks to the document
|
||||
func (page *Page) TranslateMarks(ctx *BuildContext, option *BuildOption, doc *goquery.Document, sequence *int) error {
|
||||
|
||||
if doc.Length() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ctx.translations == nil {
|
||||
ctx.translations = []Translation{}
|
||||
}
|
||||
|
||||
root := doc.First()
|
||||
translations, err := page.translateNode(root.Nodes[0], sequence)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if translations != nil {
|
||||
ctx.translations = append(ctx.translations, translations...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (page *Page) translateNode(node *html.Node, sequence *int) ([]Translation, error) {
|
||||
|
||||
translations := []Translation{}
|
||||
*sequence = *sequence + 1
|
||||
|
||||
switch node.Type {
|
||||
case html.DocumentNode:
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
trans, err := page.translateNode(child, sequence)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
translations = append(translations, trans...)
|
||||
}
|
||||
break
|
||||
|
||||
case html.ElementNode:
|
||||
|
||||
// Script
|
||||
if node.Data == "script" {
|
||||
code := goquery.NewDocumentFromNode(node).Text()
|
||||
if code != "" {
|
||||
translations := []Translation{}
|
||||
matches := transFuncRe.FindAllStringSubmatch(code, -1)
|
||||
for _, match := range matches {
|
||||
key := Namespace(page.Route, *sequence)
|
||||
translations = append(translations, Translation{
|
||||
Key: key,
|
||||
Message: match[1],
|
||||
Type: "script",
|
||||
})
|
||||
*sequence = *sequence + 1
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
sel := goquery.NewDocumentFromNode(node)
|
||||
for _, attr := range node.Attr {
|
||||
|
||||
trans, keys, err := page.translateText(attr.Val, sequence, "attr")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
raw := strings.Join(keys, ",")
|
||||
sel.SetAttr("s:trans-attr-"+attr.Key, raw)
|
||||
translations = append(translations, trans...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Node Attributes
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
trans, err := page.translateNode(child, sequence)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
translations = append(translations, trans...)
|
||||
}
|
||||
break
|
||||
|
||||
case html.TextNode:
|
||||
parentSel := goquery.NewDocumentFromNode(node.Parent)
|
||||
if _, has := parentSel.Attr("s:trans"); has {
|
||||
key := Namespace(page.Route, *sequence)
|
||||
message := strings.TrimSpace(node.Data)
|
||||
if message != "" {
|
||||
translations = append(translations, Translation{
|
||||
Key: key,
|
||||
Message: message,
|
||||
Type: "text",
|
||||
})
|
||||
parentSel.SetAttr("s:trans-node", key)
|
||||
*sequence = *sequence + 1
|
||||
}
|
||||
parentSel.RemoveAttr("s:trans")
|
||||
}
|
||||
|
||||
trans, keys, err := page.translateText(node.Data, sequence, "text")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
raw := strings.Join(keys, ",")
|
||||
parentSel.SetAttr("s:trans-text", raw)
|
||||
parentSel.RemoveAttr("s:trans")
|
||||
translations = append(translations, trans...)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return translations, nil
|
||||
}
|
||||
|
||||
func (page *Page) translateText(text string, sequence *int, typ string) ([]Translation, []string, error) {
|
||||
translations := []Translation{}
|
||||
matches := stmtRe.FindAllStringSubmatch(text, -1)
|
||||
keys := []string{}
|
||||
for _, match := range matches {
|
||||
text := strings.TrimSpace(match[1])
|
||||
transMatches := transStmtReSingle.FindAllStringSubmatch(text, -1)
|
||||
if len(transMatches) == 0 {
|
||||
transMatches = transStmtReDouble.FindAllStringSubmatch(text, -1)
|
||||
}
|
||||
for _, transMatch := range transMatches {
|
||||
message := strings.TrimSpace(transMatch[1])
|
||||
key := Namespace(page.Route, *sequence)
|
||||
keys = append(keys, key)
|
||||
translations = append(translations, Translation{
|
||||
Key: key,
|
||||
Message: message,
|
||||
Type: typ,
|
||||
})
|
||||
*sequence = *sequence + 1
|
||||
}
|
||||
}
|
||||
return translations, keys, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,14 @@ func (ctx *BuildContext) GetJitComponents() []string {
|
|||
return jitComponents
|
||||
}
|
||||
|
||||
// GetTranslations get the translations
|
||||
func (ctx *BuildContext) GetTranslations() []Translation {
|
||||
if ctx.translations == nil {
|
||||
return []Translation{}
|
||||
}
|
||||
return ctx.translations
|
||||
}
|
||||
|
||||
// GetJitComponents get the just in time components
|
||||
func (globalCtx *GlobalBuildContext) GetJitComponents() []string {
|
||||
if globalCtx.jitComponents == nil {
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ const initScriptTmpl = `
|
|||
`
|
||||
|
||||
const i118nScriptTmpl = `
|
||||
function L(key) {
|
||||
return key;
|
||||
function __m(message) {
|
||||
return message;
|
||||
}
|
||||
`
|
||||
|
||||
|
|
|
|||
|
|
@ -29,21 +29,20 @@ type Setting struct {
|
|||
|
||||
// Page is the struct for the page
|
||||
type Page struct {
|
||||
Route string `json:"route"`
|
||||
Name string `json:"name,omitempty"`
|
||||
CacheStore string `json:"-"`
|
||||
TemplateID string `json:"-"`
|
||||
SuiID string `json:"-"`
|
||||
Config *PageConfig `json:"-"`
|
||||
Path string `json:"-"`
|
||||
Codes SourceCodes `json:"-"`
|
||||
Document []byte `json:"-"`
|
||||
GlobalData []byte `json:"-"`
|
||||
Attrs map[string]string `json:"-"`
|
||||
Attributes []html.Attribute `json:"-"`
|
||||
Translations []Translation `json:"-"` // will be deprecated
|
||||
namespace string `json:"-"`
|
||||
parent *Page `json:"-"`
|
||||
Route string `json:"route"`
|
||||
Name string `json:"name,omitempty"`
|
||||
CacheStore string `json:"-"`
|
||||
TemplateID string `json:"-"`
|
||||
SuiID string `json:"-"`
|
||||
Config *PageConfig `json:"-"`
|
||||
Path string `json:"-"`
|
||||
Codes SourceCodes `json:"-"`
|
||||
Document []byte `json:"-"`
|
||||
GlobalData []byte `json:"-"`
|
||||
Attrs map[string]string `json:"-"`
|
||||
Attributes []html.Attribute `json:"-"`
|
||||
namespace string `json:"-"`
|
||||
parent *Page `json:"-"`
|
||||
}
|
||||
|
||||
// BuildContext is the struct for the build context
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ func (page *Page) Build(globalCtx *core.GlobalBuildContext, option *core.BuildOp
|
|||
}
|
||||
|
||||
// Save the locale files
|
||||
err = page.writeLocaleFiles(option.Data)
|
||||
err = page.writeLocaleFiles(ctx, option.Data)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
|
@ -272,7 +272,7 @@ func (page *Page) BuildAsComponent(globalCtx *core.GlobalBuildContext, option *c
|
|||
}
|
||||
|
||||
// Save the locale files
|
||||
err = page.writeLocaleFiles(option.Data)
|
||||
err = page.writeLocaleFiles(ctx, option.Data)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
|
@ -415,10 +415,14 @@ func (page *Page) locale(name string) core.Locale {
|
|||
return locale
|
||||
}
|
||||
|
||||
func (page *Page) writeLocaleFiles(data map[string]interface{}) error {
|
||||
func (page *Page) writeLocaleFiles(ctx *core.BuildContext, data map[string]interface{}) error {
|
||||
|
||||
// No translations
|
||||
if len(page.Page.Translations) == 0 {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
translations := ctx.GetTranslations()
|
||||
if len(translations) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -428,7 +432,7 @@ func (page *Page) writeLocaleFiles(data map[string]interface{}) error {
|
|||
// Init Data
|
||||
keys := map[string]string{}
|
||||
messages := map[string]string{}
|
||||
for _, t := range page.Page.Translations {
|
||||
for _, t := range translations {
|
||||
keys[t.Key] = t.Message
|
||||
messages[t.Message] = t.Message
|
||||
}
|
||||
|
|
|
|||
|
|
@ -279,12 +279,11 @@ func (tmpl *Template) CreateEmptyPage(route string, setting *core.PageSetting) (
|
|||
page := &Page{
|
||||
tmpl: tmpl,
|
||||
Page: &core.Page{
|
||||
Route: route,
|
||||
TemplateID: tmpl.ID,
|
||||
SuiID: tmpl.local.ID,
|
||||
Path: filepath.Join(tmpl.Root, route),
|
||||
Name: name,
|
||||
Translations: []core.Translation{},
|
||||
Route: route,
|
||||
TemplateID: tmpl.ID,
|
||||
SuiID: tmpl.local.ID,
|
||||
Path: filepath.Join(tmpl.Root, route),
|
||||
Name: name,
|
||||
Codes: core.SourceCodes{
|
||||
HTML: core.Source{File: fmt.Sprintf("%s.html", name)},
|
||||
CSS: core.Source{File: fmt.Sprintf("%s.css", name)},
|
||||
|
|
@ -350,12 +349,11 @@ func (tmpl *Template) getPage(route, file string) (core.IPage, error) {
|
|||
return &Page{
|
||||
tmpl: tmpl,
|
||||
Page: &core.Page{
|
||||
Route: route,
|
||||
Path: path,
|
||||
Name: name,
|
||||
TemplateID: tmpl.ID,
|
||||
SuiID: tmpl.local.ID,
|
||||
Translations: []core.Translation{},
|
||||
Route: route,
|
||||
Path: path,
|
||||
Name: name,
|
||||
TemplateID: tmpl.ID,
|
||||
SuiID: tmpl.local.ID,
|
||||
Codes: core.SourceCodes{
|
||||
HTML: core.Source{File: fmt.Sprintf("%s%s", name, filepath.Ext(file))},
|
||||
CSS: core.Source{File: fmt.Sprintf("%s.css", name)},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue