diff --git a/sui/core/build.go b/sui/core/build.go index 372108d1..a2f4a9af 100644 --- a/sui/core/build.go +++ b/sui/core/build.go @@ -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 - -} diff --git a/sui/core/compile.go b/sui/core/compile.go index 6ab387c1..27a67fae 100644 --- a/sui/core/compile.go +++ b/sui/core/compile.go @@ -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(``+"\n", rawStyles)) body.Children().First().AppendHtml(fmt.Sprintf(``+"\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 "" } + +// 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 +} diff --git a/sui/core/context.go b/sui/core/context.go index 36233a38..2c3be60e 100644 --- a/sui/core/context.go +++ b/sui/core/context.go @@ -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 { diff --git a/sui/core/injections.go b/sui/core/injections.go index 0b5d73cc..8cd804cd 100644 --- a/sui/core/injections.go +++ b/sui/core/injections.go @@ -70,8 +70,8 @@ const initScriptTmpl = ` ` const i118nScriptTmpl = ` - function L(key) { - return key; + function __m(message) { + return message; } ` diff --git a/sui/core/types.go b/sui/core/types.go index 5c1b11f8..518270d9 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -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 diff --git a/sui/storages/local/build.go b/sui/storages/local/build.go index abd7d892..2b41d012 100644 --- a/sui/storages/local/build.go +++ b/sui/storages/local/build.go @@ -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 } diff --git a/sui/storages/local/page.go b/sui/storages/local/page.go index f94babbb..36b334b0 100644 --- a/sui/storages/local/page.go +++ b/sui/storages/local/page.go @@ -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)},