소스 검색

Support explicit translation ID and dotted namespaces in translation extraction (#9192)

Some translations, especially single words or other short
labels for buttons and the like, may not be transferable between
contexts even if they happen to be equal in English. In these cases,
setting an explicit translation ID is important for context separation.
Angular Translate also supports nested JSON in translation tables,
addressed using `.` as namespace separator; this enhancement makes use
of this when extracting translation with an explicit translation ID.
Emil Lundberg 1 년 전
부모
커밋
a1ad020b63
4개의 변경된 파일34개의 추가작업 그리고 11개의 파일을 삭제
  1. 5 0
      gui/default/assets/lang/lang-en.json
  2. 1 0
      gui/default/index.html
  3. 1 0
      gui/default/syncthing/app.js
  4. 27 11
      script/translate.go

+ 5 - 0
gui/default/assets/lang/lang-en.json

@@ -538,6 +538,11 @@
     "modified": "modified",
     "permit": "permit",
     "seconds": "seconds",
+    "test": {
+        "translation": {
+            "dummy": "(This is just a test string for nested translation namespaces. This does not need to be translated.)"
+        }
+    },
     "theme-name-black": "Black",
     "theme-name-dark": "Dark",
     "theme-name-default": "Default",

+ 1 - 0
gui/default/index.html

@@ -1091,5 +1091,6 @@
   <script type="text/javascript" src="syncthing/app.js"></script>
   <!-- / gui application code -->
 
+  <div translate="test.translation.dummy" style="display: none;">(This is just a test string for nested translation namespaces. This does not need to be translated.)</div>
 </body>
 </html>

+ 1 - 0
gui/default/syncthing/app.js

@@ -26,6 +26,7 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
         prefix: 'assets/lang/lang-',
         suffix: '.json'
     });
+    $translateProvider.fallbackLanguage('en');
 
     LocaleServiceProvider.setAvailableLocales(validLangs);
     LocaleServiceProvider.setDefaultLocale('en');

+ 27 - 11
script/translate.go

@@ -21,7 +21,7 @@ import (
 	"golang.org/x/net/html"
 )
 
-var trans = make(map[string]string)
+var trans = make(map[string]interface{})
 var attrRe = regexp.MustCompile(`\{\{\s*'([^']+)'\s+\|\s+translate\s*\}\}`)
 var attrReCond = regexp.MustCompile(`\{\{.+\s+\?\s+'([^']+)'\s+:\s+'([^']+)'\s+\|\s+translate\s*\}\}`)
 
@@ -41,6 +41,7 @@ var aboutRe = regexp.MustCompile(`^([^/]+/[^/]+|(The Go Pro|Font Awesome ).+|Bui
 
 func generalNode(n *html.Node, filename string) {
 	translate := false
+	translationId := ""
 	if n.Type == html.ElementNode {
 		if n.Data == "translate" { // for <translate>Text</translate>
 			translate = true
@@ -50,6 +51,7 @@ func generalNode(n *html.Node, filename string) {
 			for _, a := range n.Attr {
 				if a.Key == "translate" {
 					translate = true
+					translationId = a.Val
 				} else if a.Key == "id" && (a.Val == "contributor-list" ||
 					a.Val == "copyright-notices") {
 					// Don't translate a list of names and
@@ -57,11 +59,11 @@ func generalNode(n *html.Node, filename string) {
 					return
 				} else {
 					for _, matches := range attrRe.FindAllStringSubmatch(a.Val, -1) {
-						translation(matches[1])
+						translation("", matches[1])
 					}
 					for _, matches := range attrReCond.FindAllStringSubmatch(a.Val, -1) {
-						translation(matches[1])
-						translation(matches[2])
+						translation("", matches[1])
+						translation("", matches[2])
 					}
 					if a.Key == "data-content" &&
 						!noStringRe.MatchString(a.Val) {
@@ -82,16 +84,16 @@ func generalNode(n *html.Node, filename string) {
 	}
 	for c := n.FirstChild; c != nil; c = c.NextSibling {
 		if translate {
-			inTranslate(c, filename)
+			inTranslate(c, translationId, filename)
 		} else {
 			generalNode(c, filename)
 		}
 	}
 }
 
-func inTranslate(n *html.Node, filename string) {
+func inTranslate(n *html.Node, translationId string, filename string) {
 	if n.Type == html.TextNode {
-		translation(n.Data)
+		translation(translationId, n.Data)
 	} else {
 		log.Println("translate node with non-text child < (" + filename + ")")
 		log.Println(n)
@@ -102,12 +104,26 @@ func inTranslate(n *html.Node, filename string) {
 	}
 }
 
-func translation(v string) {
+func translation(id string, v string) {
+	namespace := trans
+	idParts := strings.Split(id, ".")
+	id = idParts[len(idParts)-1]
+	for _, subNamespace := range idParts[0 : len(idParts)-1] {
+		if _, ok := namespace[subNamespace]; !ok {
+			namespace[subNamespace] = make(map[string]interface{})
+		}
+		namespace = namespace[subNamespace].(map[string]interface{})
+	}
+
 	v = strings.TrimSpace(v)
-	if _, ok := trans[v]; !ok {
+	if id == "" {
+		id = v
+	}
+
+	if _, ok := namespace[id]; !ok {
 		av := strings.Replace(v, "{%", "{{", -1)
 		av = strings.Replace(av, "%}", "}}", -1)
-		trans[v] = av
+		namespace[id] = av
 	}
 }
 
@@ -136,7 +152,7 @@ func walkerFor(basePath string) filepath.WalkFunc {
 			for s := bufio.NewScanner(fd); s.Scan(); {
 				for _, re := range jsRe {
 					for _, matches := range re.FindAllStringSubmatch(s.Text(), -1) {
-						translation(matches[1])
+						translation("", matches[1])
 					}
 				}
 			}