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.
This commit is contained in:
parent
4ddaba889f
commit
c479a90c42
18
cmd/i18n.go
18
cmd/i18n.go
|
@ -29,8 +29,8 @@ func handleGetI18nLang(c echo.Context) error {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid language code.")
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid language code.")
|
||||||
}
|
}
|
||||||
|
|
||||||
i, err := getI18nLang(lang, app.fs)
|
i, ok, err := getI18nLang(lang, app.fs)
|
||||||
if err != nil {
|
if err != nil && !ok {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Unknown language.")
|
return echo.NewHTTPError(http.StatusBadRequest, "Unknown language.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,29 +65,31 @@ func getI18nLangList(lang string, app *App) ([]i18nLang, error) {
|
||||||
return out, nil
|
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"
|
const def = "en"
|
||||||
|
|
||||||
b, err := fs.Read(fmt.Sprintf("/i18n/%s.json", def))
|
b, err := fs.Read(fmt.Sprintf("/i18n/%s.json", def))
|
||||||
if err != nil {
|
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.
|
// Initialize with the default language.
|
||||||
i, err := i18n.New(b)
|
i, err := i18n.New(b)
|
||||||
if err != nil {
|
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.
|
// Load the selected language on top of it.
|
||||||
b, err = fs.Read(fmt.Sprintf("/i18n/%s.json", lang))
|
b, err = fs.Read(fmt.Sprintf("/i18n/%s.json", lang))
|
||||||
if err != nil {
|
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 {
|
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
|
||||||
}
|
}
|
||||||
|
|
24
cmd/init.go
24
cmd/init.go
|
@ -82,6 +82,7 @@ func initFlags() {
|
||||||
f.Bool("version", false, "current version of the build")
|
f.Bool("version", false, "current version of the build")
|
||||||
f.Bool("new-config", false, "generate sample config file")
|
f.Bool("new-config", false, "generate sample config file")
|
||||||
f.String("static-dir", "", "(optional) path to directory with static files")
|
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")
|
f.Bool("yes", false, "assume 'yes' to prompts, eg: during --install")
|
||||||
if err := f.Parse(os.Args[1:]); err != nil {
|
if err := f.Parse(os.Args[1:]); err != nil {
|
||||||
lo.Fatalf("error loading flags: %v", err)
|
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
|
// initFileSystem initializes the stuffbin FileSystem to provide
|
||||||
// access to bunded static assets to the app.
|
// 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.
|
// Get the executable's path.
|
||||||
path, err := os.Executable()
|
path, err := os.Executable()
|
||||||
if err != nil {
|
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 != "" {
|
if staticDir != "" {
|
||||||
lo.Printf("loading static files from: %v", staticDir)
|
lo.Printf("loading static files from: %v", staticDir)
|
||||||
fStatic, err := stuffbin.NewLocalFS("/", []string{
|
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)
|
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
|
return fs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,10 +276,14 @@ func initConstants() *constants {
|
||||||
// and then the selected language is loaded on top of it so that if there are
|
// 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.
|
// missing translations in it, the default English translations show up.
|
||||||
func initI18n(lang string, fs stuffbin.FileSystem) *i18n.I18n {
|
func initI18n(lang string, fs stuffbin.FileSystem) *i18n.I18n {
|
||||||
i, err := getI18nLang(lang, fs)
|
i, ok, err := getI18nLang(lang, fs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if ok {
|
||||||
|
lo.Println(err)
|
||||||
|
} else {
|
||||||
lo.Fatal(err)
|
lo.Fatal(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,7 @@ func newConfigFile() error {
|
||||||
|
|
||||||
// Initialize the static file system into which all
|
// Initialize the static file system into which all
|
||||||
// required static assets (.sql, .js files etc.) are loaded.
|
// required static assets (.sql, .js files etc.) are loaded.
|
||||||
fs := initFS("")
|
fs := initFS("", "")
|
||||||
b, err := fs.Read("config.toml.sample")
|
b, err := fs.Read("config.toml.sample")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error reading sample config (is binary stuffed?): %v", err)
|
return fmt.Errorf("error reading sample config (is binary stuffed?): %v", err)
|
||||||
|
|
|
@ -106,7 +106,7 @@ func init() {
|
||||||
|
|
||||||
// Connect to the database, load the filesystem to read SQL queries.
|
// Connect to the database, load the filesystem to read SQL queries.
|
||||||
db = initDB()
|
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
|
// Installer mode? This runs before the SQL queries are loaded and prepared
|
||||||
// as the installer needs to work on an empty DB.
|
// as the installer needs to work on an empty DB.
|
||||||
|
|
Loading…
Reference in New Issue