Browse Source

Add support for loading external i18n language files.

The new `--i18n-dir` directory allows the loading of an external
directory of i18n JSON files, milar to have `--static-dir`
works. New languages can be added and existing language files
can be customized this way.

This commit changes file loading behaviour so that invalid or
non-existent don't halt the execution of the app completely but
merely throw a warning and continue with the default (en) lang.
Kailash Nadh 4 năm trước cách đây
mục cha
commit
c479a90c42
4 tập tin đã thay đổi với 34 bổ sung14 xóa
  1. 10 8
      cmd/i18n.go
  2. 22 4
      cmd/init.go
  3. 1 1
      cmd/install.go
  4. 1 1
      cmd/main.go

+ 10 - 8
cmd/i18n.go

@@ -29,8 +29,8 @@ func handleGetI18nLang(c echo.Context) error {
 		return echo.NewHTTPError(http.StatusBadRequest, "Invalid language code.")
 	}
 
-	i, err := getI18nLang(lang, app.fs)
-	if err != nil {
+	i, ok, err := getI18nLang(lang, app.fs)
+	if err != nil && !ok {
 		return echo.NewHTTPError(http.StatusBadRequest, "Unknown language.")
 	}
 
@@ -65,29 +65,31 @@ func getI18nLangList(lang string, app *App) ([]i18nLang, error) {
 	return out, nil
 }
 
-func getI18nLang(lang string, fs stuffbin.FileSystem) (*i18n.I18n, error) {
+// The bool indicates whether the specified language could be loaded. If it couldn't
+// be, the app shouldn't halt but throw a warning.
+func getI18nLang(lang string, fs stuffbin.FileSystem) (*i18n.I18n, bool, error) {
 	const def = "en"
 
 	b, err := fs.Read(fmt.Sprintf("/i18n/%s.json", def))
 	if err != nil {
-		return nil, fmt.Errorf("error reading default i18n language file: %s: %v", def, err)
+		return nil, false, fmt.Errorf("error reading default i18n language file: %s: %v", def, err)
 	}
 
 	// Initialize with the default language.
 	i, err := i18n.New(b)
 	if err != nil {
-		return nil, fmt.Errorf("error unmarshalling i18n language: %v", err)
+		return nil, false, fmt.Errorf("error unmarshalling i18n language: %s: %v", lang, err)
 	}
 
 	// Load the selected language on top of it.
 	b, err = fs.Read(fmt.Sprintf("/i18n/%s.json", lang))
 	if err != nil {
-		return nil, fmt.Errorf("error reading i18n language file: %v", err)
+		return i, true, fmt.Errorf("error reading i18n language file: %s: %v", lang, err)
 	}
 
 	if err := i.Load(b); err != nil {
-		return nil, fmt.Errorf("error loading i18n language file: %v", err)
+		return i, true, fmt.Errorf("error loading i18n language file: %s: %v", lang, err)
 	}
 
-	return i, nil
+	return i, true, nil
 }

+ 22 - 4
cmd/init.go

@@ -82,6 +82,7 @@ func initFlags() {
 	f.Bool("version", false, "current version of the build")
 	f.Bool("new-config", false, "generate sample config file")
 	f.String("static-dir", "", "(optional) path to directory with static files")
+	f.String("i18n-dir", "", "(optional) path to directory with i18n language files")
 	f.Bool("yes", false, "assume 'yes' to prompts, eg: during --install")
 	if err := f.Parse(os.Args[1:]); err != nil {
 		lo.Fatalf("error loading flags: %v", err)
@@ -107,7 +108,7 @@ func initConfigFiles(files []string, ko *koanf.Koanf) {
 
 // initFileSystem initializes the stuffbin FileSystem to provide
 // access to bunded static assets to the app.
-func initFS(staticDir string) stuffbin.FileSystem {
+func initFS(staticDir, i18nDir string) stuffbin.FileSystem {
 	// Get the executable's path.
 	path, err := os.Executable()
 	if err != nil {
@@ -144,7 +145,7 @@ func initFS(staticDir string) stuffbin.FileSystem {
 		}
 	}
 
-	// Optional static directory to override files.
+	// Optional static directory to override static files.
 	if staticDir != "" {
 		lo.Printf("loading static files from: %v", staticDir)
 		fStatic, err := stuffbin.NewLocalFS("/", []string{
@@ -161,6 +162,19 @@ func initFS(staticDir string) stuffbin.FileSystem {
 			lo.Fatalf("error merging static directory: %s: %v", staticDir, err)
 		}
 	}
+
+	// Optional static directory to override i18n language files.
+	if i18nDir != "" {
+		lo.Printf("loading i18n language files from: %v", i18nDir)
+		fi18n, err := stuffbin.NewLocalFS("/", []string{i18nDir + ":/i18n"}...)
+		if err != nil {
+			lo.Fatalf("failed reading i18n directory: %s: %v", i18nDir, err)
+		}
+
+		if err := fs.Merge(fi18n); err != nil {
+			lo.Fatalf("error merging i18n directory: %s: %v", i18nDir, err)
+		}
+	}
 	return fs
 }
 
@@ -262,9 +276,13 @@ func initConstants() *constants {
 // and then the selected language is loaded on top of it so that if there are
 // missing translations in it, the default English translations show up.
 func initI18n(lang string, fs stuffbin.FileSystem) *i18n.I18n {
-	i, err := getI18nLang(lang, fs)
+	i, ok, err := getI18nLang(lang, fs)
 	if err != nil {
-		lo.Fatal(err)
+		if ok {
+			lo.Println(err)
+		} else {
+			lo.Fatal(err)
+		}
 	}
 	return i
 }

+ 1 - 1
cmd/install.go

@@ -166,7 +166,7 @@ func newConfigFile() error {
 
 	// Initialize the static file system into which all
 	// required static assets (.sql, .js files etc.) are loaded.
-	fs := initFS("")
+	fs := initFS("", "")
 	b, err := fs.Read("config.toml.sample")
 	if err != nil {
 		return fmt.Errorf("error reading sample config (is binary stuffed?): %v", err)

+ 1 - 1
cmd/main.go

@@ -106,7 +106,7 @@ func init() {
 
 	// Connect to the database, load the filesystem to read SQL queries.
 	db = initDB()
-	fs = initFS(ko.String("static-dir"))
+	fs = initFS(ko.String("static-dir"), ko.String("i18n-dir"))
 
 	// Installer mode? This runs before the SQL queries are loaded and prepared
 	// as the installer needs to work on an empty DB.