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 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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tcl-msgcat.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
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
@@ -0,0 +1,5 @@
1
+ module Tcl
2
+ module Msgcat
3
+ VERSION = "0.5"
4
+ end
5
+ 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
@@ -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: []