From c479a90c4234244b21026145351c69f11de384db Mon Sep 17 00:00:00 2001 From: Kailash Nadh Date: Sat, 17 Apr 2021 14:26:56 +0530 Subject: [PATCH] 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. --- cmd/i18n.go | 18 ++++++++++-------- cmd/init.go | 26 ++++++++++++++++++++++---- cmd/install.go | 2 +- cmd/main.go | 2 +- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/cmd/i18n.go b/cmd/i18n.go index 66bb0e9..3b429ce 100644 --- a/cmd/i18n.go +++ b/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 } diff --git a/cmd/init.go b/cmd/init.go index da8164b..5dd3811 100644 --- a/cmd/init.go +++ b/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 } diff --git a/cmd/install.go b/cmd/install.go index fbfb9a8..4bb9008 100644 --- a/cmd/install.go +++ b/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) diff --git a/cmd/main.go b/cmd/main.go index 7e11afa..0e98223 100644 --- a/cmd/main.go +++ b/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.