tcl-msgcat 0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +62 -0
- data/Rakefile +2 -0
- data/bin/msgcat +39 -0
- data/lib/tcl/msgcat/catalog.rb +20 -0
- data/lib/tcl/msgcat/parser.rb +91 -0
- data/lib/tcl/msgcat/renderer.rb +47 -0
- data/lib/tcl/msgcat/version.rb +5 -0
- data/lib/tcl/msgcat.rb +50 -0
- data/tcl-msgcat.gemspec +23 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1d08c896d423e775ef1b282c5399bd57087c3ef7
|
4
|
+
data.tar.gz: c66f9414471f9a514af85e9ea1cf7f6eadf36204
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e290d35a7c371da396f1a6705b7d464fddecaab9ee44748415f9d0a7569781ed3b8128995f678acb7214e291c92c90145b3a2dc3ee3e40308e5541d94ff934bf
|
7
|
+
data.tar.gz: b93c2286e49c180b27d5635918b993cecc804f48a357bfa400fa961dbf80a657fc5527b0ef043285648b9427d3b9e277864b985ad741869b80bbe5ab9f67082a
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Robert McLeod
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Tcl::Msgcat
|
2
|
+
|
3
|
+
Library for parsing, rendering and merging TCL msgcat (read i18n) files.
|
4
|
+
|
5
|
+
TCL msgcat files are yuk, and a legacy format. Unfortunately some of us are stuck with systems that still use these files
|
6
|
+
for i18n in TCL applications. Managing these files can be a bit annoying, especially when they are large and you have
|
7
|
+
many different people translating them. What's more, TCL doesn't report line numbers in it's error messages!!! So if you have
|
8
|
+
an error in your 7000 line msgcat file, good luck finding it!
|
9
|
+
|
10
|
+
This library aims to solve that problem by storing the translations in JSON and compiling them to the TCL msgcat format. JSON
|
11
|
+
is easily lintable and most syntax checkers tell you which line any errors are on. Plus it is a modern standard and supported
|
12
|
+
by lots of languages which are better than TCL so you can programmatically update changes between your root message file and
|
13
|
+
your translation files. Which is another thing this library does.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'tcl-msgcat'
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install tcl-msgcat
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
You'll want to convert your existing files first. You can do this with the following command:
|
34
|
+
|
35
|
+
msgcat parse root.msg > root.json
|
36
|
+
msgcat parse zh_tw.msg > zh_tw.json
|
37
|
+
|
38
|
+
And render it back to the msgcat file:
|
39
|
+
|
40
|
+
msgcat render root.json > root.test.msg
|
41
|
+
|
42
|
+
OK, now say you have added a bunch of strings to your `root.json` for some new stuff in the application. You
|
43
|
+
will want to update all your other files with those strings, and send them off to your translators. For this
|
44
|
+
there is the `merge` action with a few ways to use it:
|
45
|
+
|
46
|
+
msgcat merge root.json fr.json de.json es.json # specify files individually
|
47
|
+
msgcat merge root.json *.json # or glob them (root.json is automatically excluded)
|
48
|
+
|
49
|
+
Now you have got your files back from the translators, put them through a [linter](http://jsonlint.com/)
|
50
|
+
and found no errors. You are ready to put those new translations back into your application. Use the `compile`
|
51
|
+
action to run the render command on all your json files:
|
52
|
+
|
53
|
+
msgcat compile ui/msgs # only one arg needed if you don't segregate source/compiled
|
54
|
+
msgcat compile ui/msgs/source ui/msgs # otherwise specify the source and compile paths seperately
|
55
|
+
|
56
|
+
## Contributing
|
57
|
+
|
58
|
+
1. Fork it ( https://github.com/AutogrowSystems/tcl-msgcat/fork )
|
59
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
60
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
61
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
62
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/msgcat
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'tcl-msgcat'
|
4
|
+
|
5
|
+
case ARGV[0]
|
6
|
+
when "parse", "p"
|
7
|
+
file = ARGV[1]
|
8
|
+
puts Tcl::Msgcat.parse(file).to_json
|
9
|
+
when "render", "r"
|
10
|
+
file = ARGV[1]
|
11
|
+
puts Tcl::Msgcat.render(file).to_s
|
12
|
+
when "merge", "m"
|
13
|
+
root = ARGV[1]
|
14
|
+
files = ARGV[2..-1]
|
15
|
+
Tcl::Msgcat.merge(root, files)
|
16
|
+
when "compile"
|
17
|
+
source_path = ARGV[1]
|
18
|
+
target_path = ARGV[2] || ARGV[1]
|
19
|
+
|
20
|
+
Tcl::Msgcat.compile(source_path, target_path)
|
21
|
+
else
|
22
|
+
puts <<-EOH
|
23
|
+
Usage: msgcat <action> <arguments>
|
24
|
+
|
25
|
+
Actions:
|
26
|
+
parse <file> Parse a msgcat file and output JSON
|
27
|
+
render <file> Parse a JSON file and output msgcat
|
28
|
+
merge <root> <files> Merge new keys from root into the files (glob pattern)
|
29
|
+
compile <source> [target] Compile from the source into the target
|
30
|
+
|
31
|
+
Examples:
|
32
|
+
msgcat parse ROOT.msg > root.json
|
33
|
+
msgcat render root.json > ROOT.msg
|
34
|
+
msgcat merge root.json path/to/translations/*.json
|
35
|
+
msgcat merge root.json zh_tw.json fr.json de.json
|
36
|
+
msgcat compile path/to/my/json/files path/to/my/msgcat/files
|
37
|
+
msgcat compile path/to/my/json/msgcat/files
|
38
|
+
EOH
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Tcl
|
2
|
+
module Msgcat
|
3
|
+
class Catalog
|
4
|
+
attr_reader :msgs
|
5
|
+
def initialize(msgs)
|
6
|
+
@msgs = msgs
|
7
|
+
end
|
8
|
+
|
9
|
+
def merge!(catalog)
|
10
|
+
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
11
|
+
@msgs = catalog.msgs.merge(@msgs, &merger)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.load(file)
|
15
|
+
msgs = JSON.parse(File.read(file))
|
16
|
+
Tcl::Msgcat::Catalog.new(msgs)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Tcl
|
2
|
+
module Msgcat
|
3
|
+
|
4
|
+
# Parses a msgcat file into a ruby hash
|
5
|
+
# which can then be converted to whatever
|
6
|
+
class Parser
|
7
|
+
attr_reader :msgs
|
8
|
+
|
9
|
+
def initialize(file)
|
10
|
+
raise ArgumentError, "File not found" unless File.exist? file
|
11
|
+
|
12
|
+
@msgs = {}
|
13
|
+
@file = file
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse
|
17
|
+
lines = File.read(@file).lines
|
18
|
+
scopes = []
|
19
|
+
|
20
|
+
# strip the comments out
|
21
|
+
lines.reject! {|l| l.match(/^\s?#/) }
|
22
|
+
|
23
|
+
# run through each line and do an action
|
24
|
+
# depending on the kind of line it is
|
25
|
+
lines.each do |line|
|
26
|
+
|
27
|
+
# start of a message group/namespace definition
|
28
|
+
if match = line.match(/msgs (\S+) \{/) # msg ::site::irrigation {
|
29
|
+
scopes << match[1]
|
30
|
+
next
|
31
|
+
end
|
32
|
+
|
33
|
+
# end of the message group/namespace definition
|
34
|
+
if match = line.match(/\}/) # }
|
35
|
+
scopes.pop
|
36
|
+
next
|
37
|
+
end
|
38
|
+
|
39
|
+
# an actual message definition
|
40
|
+
if match = line.match(/m (\S+) "(.+)"/) # m label "the actual string"
|
41
|
+
add(scopes, match[1], match[2])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# Adds a key and translation to the msg hash
|
49
|
+
#
|
50
|
+
# Uses eval to dynamically create a multi dimensional
|
51
|
+
# hash from the scopes array that is given
|
52
|
+
#
|
53
|
+
def add(scopes, name, string)
|
54
|
+
chain = []
|
55
|
+
|
56
|
+
# run through each scope
|
57
|
+
scopes.each_with_index do |k, i|
|
58
|
+
chain = []
|
59
|
+
|
60
|
+
# build the chain of send commands from the top level
|
61
|
+
# to the level of the current scope, for each iteration
|
62
|
+
#
|
63
|
+
# e.g. for iteration:
|
64
|
+
#
|
65
|
+
# first: hash[:scope1]
|
66
|
+
# second: hash[:scope1][:scope2]
|
67
|
+
# third: hash[:scope1][:scope2][:scope3]
|
68
|
+
subkeys = scopes[0..i]
|
69
|
+
subkeys.each do |k|
|
70
|
+
chain << "send('[]', '#{k}')"
|
71
|
+
end
|
72
|
+
|
73
|
+
# if the (sub)key doesn't exist for this (chain of) scopes
|
74
|
+
# then set it to an empty hash
|
75
|
+
if eval("@msgs.#{chain.join(".")}").nil?
|
76
|
+
set_chain = chain[0..-2] + ["send('[]=', '#{subkeys.last}', {})"]
|
77
|
+
eval("@msgs.#{set_chain.join(".")}")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# set the value a the end of the chain
|
82
|
+
chain << "send('[]=', '#{name}', %q[#{string}])"
|
83
|
+
eval("@msgs.#{chain.join(".")}")
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_json(pretty=true)
|
87
|
+
pretty ? JSON.pretty_generate(@msgs) : @msgs.to_json
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Tcl
|
2
|
+
module Msgcat
|
3
|
+
class Renderer
|
4
|
+
attr_reader :msgs, :lines
|
5
|
+
def initialize(msgs)
|
6
|
+
@msgs = msgs
|
7
|
+
@lines = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def render
|
11
|
+
@lines << "package require mchelper"
|
12
|
+
@lines << "msg_lang {}"
|
13
|
+
@lines << ""
|
14
|
+
_render(@msgs)
|
15
|
+
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
@lines.join("\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# A recursive function that calls itself
|
26
|
+
# when the value of a key is a hash so it can
|
27
|
+
# iterate through every dimension of the hash
|
28
|
+
# to get at the translation labels and strings
|
29
|
+
def _render(msgs, level=0)
|
30
|
+
msgs.each do |key, value|
|
31
|
+
if value.is_a? Hash
|
32
|
+
@lines << "msgs #{key} {".rpad(" ", level)
|
33
|
+
level+=1
|
34
|
+
_render(value, level) # render any child hashes or translation labels/strings
|
35
|
+
level-=1
|
36
|
+
@lines << "}".rpad(" ", level)
|
37
|
+
@lines << ""
|
38
|
+
end
|
39
|
+
|
40
|
+
if value.is_a? String
|
41
|
+
@lines << "m #{key} \"#{value}\"".rpad(" ", level)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/tcl/msgcat.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'json'
|
2
|
+
require "tcl/msgcat/version"
|
3
|
+
require "tcl/msgcat/catalog"
|
4
|
+
require "tcl/msgcat/parser"
|
5
|
+
require "tcl/msgcat/renderer"
|
6
|
+
|
7
|
+
module Tcl
|
8
|
+
module Msgcat
|
9
|
+
def parse(msgcat_file)
|
10
|
+
Tcl::Msgcat::Parser.new(msgcat_file).parse
|
11
|
+
end
|
12
|
+
|
13
|
+
def render(json_file)
|
14
|
+
raise ArgumentError, "File not found: #{json_file}" unless File.exist? json_file
|
15
|
+
msgs = JSON.parse(File.read(json_file))
|
16
|
+
Tcl::Msgcat::Renderer.new(msgs).render
|
17
|
+
end
|
18
|
+
|
19
|
+
def merge(root_file, translation_files=[])
|
20
|
+
if translation_files.is_a? String
|
21
|
+
translation_files = Dir[translation_files]
|
22
|
+
end
|
23
|
+
|
24
|
+
root = Tcl::Msgcat::Catalog.load(root_file)
|
25
|
+
|
26
|
+
translation_files.each do |file|
|
27
|
+
next if File.basename(file) == File.basename(root_file)
|
28
|
+
print "Merging new translations into #{file}... "
|
29
|
+
catalog = Tcl::Msgcat::Catalog.load(file)
|
30
|
+
catalog.merge!(root)
|
31
|
+
File.open(file, "w") {|f| f.write catalog.to_json }
|
32
|
+
puts "done"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def compile(source, target)
|
37
|
+
raise ArgumentError, "Not a directory: #{source}" unless File.directory? source
|
38
|
+
raise ArgumentError, "Not a directory: #{target}" unless File.directory? target
|
39
|
+
|
40
|
+
Dir["#{source}/*.json"].each do |src|
|
41
|
+
dst = File.basename(src, ".json")+".msg"
|
42
|
+
print "Compiling #{src} to #{target}/#{dst}... "
|
43
|
+
msgs = JSON.parse(File.read(src))
|
44
|
+
renderer = Tcl::Msgcat::Renderer.new(msgs).render
|
45
|
+
File.write("#{target}/#{dst}") {|f| f.write renderer.to_s }
|
46
|
+
puts "done"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/tcl-msgcat.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tcl/msgcat/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "tcl-msgcat"
|
8
|
+
spec.version = Tcl::Msgcat::VERSION
|
9
|
+
spec.authors = ["Robert McLeod"]
|
10
|
+
spec.email = ["robert@autogrow.com"]
|
11
|
+
spec.summary = %q{Library for parsing, rendering and merging TCL msgcat files}
|
12
|
+
spec.description = %q{Library for parsing, rendering and merging TCL msgcat files}
|
13
|
+
spec.homepage = "https://github.com/AutogrowSystems/tcl-msgcat"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tcl-msgcat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.5'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Robert McLeod
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: Library for parsing, rendering and merging TCL msgcat files
|
42
|
+
email:
|
43
|
+
- robert@autogrow.com
|
44
|
+
executables:
|
45
|
+
- msgcat
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".gitignore"
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- bin/msgcat
|
55
|
+
- lib/tcl/msgcat.rb
|
56
|
+
- lib/tcl/msgcat/catalog.rb
|
57
|
+
- lib/tcl/msgcat/parser.rb
|
58
|
+
- lib/tcl/msgcat/renderer.rb
|
59
|
+
- lib/tcl/msgcat/version.rb
|
60
|
+
- tcl-msgcat.gemspec
|
61
|
+
homepage: https://github.com/AutogrowSystems/tcl-msgcat
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 2.2.2
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: Library for parsing, rendering and merging TCL msgcat files
|
85
|
+
test_files: []
|