Convert localization source files into go-i18n message files (JSON, TOML, YAML).
mk2i18n reads common markup formats like Java .properties, JSON, TOML, YAML, and XML, then emits go-i18n compatible files where each flattened key becomes a message with description and other fields.
- Inputs:
.properties.json.toml.yaml/.yml.xml
- Outputs:
.json.toml.yaml
- Centralize diverse source formats for translation into a single go-i18n schema
- Keep message IDs stable via deterministic key flattening
- Simple CLI and importable Go package
With Go installed:
go install github.com/s-nix/mk2i18n@latestBuild from source in this repo:
go build -o mk2i18n.exe .You can then run mk2i18n.exe (Windows) or ./mk2i18n (other platforms).
Flags:
- -i string Input file path. Supported: .json, .toml, .yaml, .yml, .xml, .properties
- -p string Output file path. Supported: .json, .toml, .yaml
Examples:
mk2i18n.exe -i ./strings.properties -p ./out/messages.yaml
mk2i18n.exe -i ./en.json -p ./dist/en.yaml
mk2i18n.exe -i ./messages.toml -p ./messages.yaml
mk2i18n.exe -i ./strings.xml -p ./strings.json
mk2i18n.exe -i ./bundle.yaml -p ./bundle.tomlBehavior:
- The tool validates the input and output extensions and paths.
- If the output directory does not exist, it will be created.
- If the exact output file already exists, the CLI will not overwrite it and will exit with an error. Provide a new filename or remove the existing file.
Exit codes are non-zero on error.
The go-i18n schema this tool writes is a flat map from message IDs to an object with description and other fields. For example, given nested inputs like:
{
"greeting": {
"description": "A greeting message",
"other": "Hello"
},
"farewell": {
"description": "A farewell message",
"other": "Goodbye"
}
}The YAML output becomes:
greeting.description:
description: ""
other: A greeting message
greeting.other:
description: ""
other: Hello
farewell.description:
description: ""
other: A farewell message
farewell.other:
description: ""
other: GoodbyeJSON output is a single object with the same keys, pretty-printed:
{
"greeting.description": {
"description": "",
"other": "A greeting message"
},
"greeting.other": {
"description": "",
"other": "Hello"
},
"farewell.description": {
"description": "",
"other": "A farewell message"
},
"farewell.other": {
"description": "",
"other": "Goodbye"
}
}TOML output creates one table per flattened key:
["greeting.description"]
description = ""
other = "A greeting message"
["greeting.other"]
description = ""
other = "Hello"
["farewell.description"]
description = ""
other = "A farewell message"
["farewell.other"]
description = ""
other = "Goodbye"Note: Entries are sorted lexicographically by message ID, so the order may differ from the input but is stable.
mk2i18n normalizes all inputs to a nested map structure and then flattens it using dot-separated keys. The rules are:
- Objects/maps become dot-joined keys: parent.child.grandchild
- Arrays/slices are indexed: key.0, key.1, ...
- Mixed maps from YAML (map[any]any) are converted to map[string]any when keys are strings
- Primitive array items (e.g., ["a", "b"]) become messages
key.0,key.1with the printed value asother - Non-string scalar values are stringified
- After flattening, each leaf value becomes a message with:
- ID: the flattened key
- Other: the leaf value string
- Description: empty by default (unless the input itself already had flattened description/other pairs)
Specific sources:
- .properties: each property
a.b.c=Valuebecomes a message with IDa.b.cand otherValue. - JSON/TOML/YAML: nested documents are flattened according to the rules above.
- XML: element names form the path; repeated sibling elements are indexed; text content becomes the value.
Import and call the high-level converter:
package main
import (
"log"
"github.com/s-nix/mk2i18n/converter"
)
func main() {
if err := converter.Convert("./in.yaml", "./out/messages.toml"); err != nil {
log.Fatal(err)
}
}Or use the parsers/formatters directly:
package main
import (
"fmt"
"github.com/s-nix/mk2i18n/parser"
)
func main() {
msgs, err := parser.FromJSON("./in.json")
if err != nil { panic(err) }
yaml, err := parser.ToYAML(msgs)
if err != nil { panic(err) }
fmt.Println(yaml)
}Message type (for reference):
- ID string
- Description string
- Other string
Each message marshals to JSON/TOML/YAML as described above.
mk2i18n.exe -i ./examples/en.properties -p ./build/en.yaml
mk2i18n.exe -i ./examples/en.json -p ./build/en.toml
mk2i18n.exe -i ./examples/en.toml -p ./build/en.json
mk2i18n.exe -i ./examples/en.xml -p ./build/en.yaml
mk2i18n.exe -i ./examples/en.yaml -p ./build/en.yaml- Run tests:
go test ./...- Key packages:
converter: high-levelConvert(in, out)that routes to format-specific parsers/formatters based on file extensionsparser:FromJSON,FromTOML,FromYAML,FromXML,FromPropertiesandToJSON,ToTOML,ToYAMLparser/data_flatten.go: shared flattening logicmessage:Messagetype plus JSON/TOML/YAML marshalers
- Input file does not exist: ensure
-ipoints to a file, not a directory - Unsupported extension: check the input/output extensions listed above (CLI expects
.yaml, not.yml) - Will not overwrite output: if a file already exists at
-p, delete it or choose a different path - YAML keys not strings: YAML maps with non-string keys are ignored for those entries during flattening
- Ordering differences: output is sorted by key; compare structurally (e.g., JSON compare) rather than by line order
GPL-3.0. See LICENSE.
- go-yaml (v3) for YAML encoding/decoding
- BurntSushi/toml for TOML encoding/decoding
- magiconair/properties for .properties parsing
- stretchr/testify for the test suite