i18n-linter-rb 0.1.0

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
+ SHA256:
3
+ metadata.gz: 8be824b04d9dc87841bb4a53ded28fdc99c745db2b5ec0473aefb3feb0903430
4
+ data.tar.gz: 7cc5a3f6398b114022e1c103f639642c9200dbfda01e61e1b0e13671e675faa0
5
+ SHA512:
6
+ metadata.gz: 66b748b2fcdb6d546c0678fccc86dec7e5d9316c6f4ced5c3bd0edf3c511363988971648be72e40b3d56668b45e5b32bdf3f1197a90fd23c4860dac6355d2ca2
7
+ data.tar.gz: 9b7963b6f8c53d4e372831455ca52bcc8ac2ebb4ab8d9305a72be8e5df97a60e7db81b5d23f7650f2638b630721c2b3211d16aae1011320e8261a203b9015fa4
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "i18n_linter_rb"
4
+
5
+ I18nLinterRb.lint(ARGV)
@@ -0,0 +1,116 @@
1
+ require "optparse"
2
+ require "i18n_linter_rb/linter"
3
+
4
+ module I18nLinterRb
5
+ class Cli
6
+ DEFAULT_LOCALES_PATH = "config/locales".freeze
7
+ DEFAULT_SOURCE_LOCALE = "en".freeze
8
+ DEFAULT_TARGET_LOCALES = %w[es].freeze
9
+
10
+ USAGE_GUIDE = <<~GUIDE
11
+
12
+ No locales directory found.
13
+
14
+ i18n-linter-rb looks for a Rails locales directory by default:
15
+ config/locales/
16
+
17
+ If your translations live somewhere else, pass the path explicitly:
18
+
19
+ i18n-linter-rb --path path/to/locales
20
+ i18n-linter-rb --path path/to/locales --from en --to es fr pt
21
+
22
+ You can also create an .i18n_linter.yml file in your project root:
23
+
24
+ locales_path: path/to/locales
25
+ source_locale: en
26
+ target_locales: [es, fr]
27
+
28
+ GUIDE
29
+
30
+ def initialize(argv)
31
+ @argv = argv
32
+ @options = {}
33
+ end
34
+
35
+ def run
36
+ parse_options
37
+ resolve_locales_path
38
+
39
+ source_path = File.join(@locales_path, "#{@options[:from]}.yml")
40
+ missing = {}
41
+
42
+ @options[:to].each do |target_locale|
43
+ target_path = File.join(@locales_path, "#{target_locale}.yml")
44
+ linter = Linter.new(source_locale: @options[:from], target_locale: target_locale)
45
+ keys = linter.missing_keys_in_target(source_path, target_path)
46
+ missing[target_locale] = keys unless keys.empty?
47
+ end
48
+
49
+ report(missing)
50
+ end
51
+
52
+ private
53
+
54
+ def parse_options
55
+ parser = OptionParser.new do |opts|
56
+ opts.banner = "Usage: i18n-linter-rb [options]"
57
+
58
+ opts.on("-p", "--path PATH", "Locales directory (default: #{DEFAULT_LOCALES_PATH})") do |path|
59
+ @options[:path] = path
60
+ end
61
+
62
+ opts.on("-f", "--from LOCALE", "Source locale (default: #{DEFAULT_SOURCE_LOCALE})") do |locale|
63
+ @options[:from] = locale
64
+ end
65
+
66
+ opts.on("-t", "--to LOCALES x,y,z", Array, "Target locales (default: #{DEFAULT_TARGET_LOCALES.join(',')})") do |locales|
67
+ @options[:to] = locales
68
+ end
69
+
70
+ opts.on("-v", "--version", "Show version") do
71
+ puts I18nLinterRb::VERSION
72
+ exit
73
+ end
74
+
75
+ opts.on("-h", "--help", "Show this help") do
76
+ puts opts
77
+ exit
78
+ end
79
+ end
80
+
81
+ parser.parse!(@argv)
82
+ @options[:from] ||= load_config["source_locale"] || DEFAULT_SOURCE_LOCALE
83
+ @options[:to] ||= load_config["target_locales"] || DEFAULT_TARGET_LOCALES
84
+ end
85
+
86
+ def resolve_locales_path
87
+ @locales_path = @options[:path] || load_config["locales_path"] || DEFAULT_LOCALES_PATH
88
+
89
+ unless File.directory?(@locales_path)
90
+ abort(USAGE_GUIDE)
91
+ end
92
+ end
93
+
94
+ def load_config
95
+ @load_config ||= begin
96
+ config_path = ".i18n_linter.yml"
97
+ File.exist?(config_path) ? YAML.load_file(config_path) : {}
98
+ rescue Psych::SyntaxError => e
99
+ warn("Invalid .i18n_linter.yml: #{e.message}")
100
+ {}
101
+ end
102
+ end
103
+
104
+ def report(missing)
105
+ if missing.empty?
106
+ puts "All translations are complete."
107
+ else
108
+ missing.each do |locale, keys|
109
+ puts "Missing keys in #{locale}.yml:"
110
+ keys.each { |key| puts " - #{key}" }
111
+ end
112
+ exit 1
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,44 @@
1
+ require "yaml"
2
+
3
+ module I18nLinterRb
4
+ class Linter
5
+ attr_reader :source_locale, :target_locale
6
+
7
+ def initialize(source_locale:, target_locale:)
8
+ @source_locale = source_locale
9
+ @target_locale = target_locale
10
+ end
11
+
12
+ def missing_keys_in_target(source_path, target_path)
13
+ source_keys = locale_keys(source_path)
14
+ target_keys = locale_keys(target_path)
15
+
16
+ source_keys - target_keys
17
+ end
18
+
19
+ private
20
+
21
+ def locale_keys(path)
22
+ data = YAML.load_file(path)
23
+ flatten_hash(root_data(data)).keys
24
+ end
25
+
26
+ def root_data(data)
27
+ data.values.first || {}
28
+ end
29
+
30
+ def flatten_hash(hash, prefix = "")
31
+ result = {}
32
+
33
+ hash.each do |key, value|
34
+ if value.is_a?(Hash)
35
+ result.merge!(flatten_hash(value, "#{prefix}#{key}."))
36
+ else
37
+ result["#{prefix}#{key}"] = value
38
+ end
39
+ end
40
+
41
+ result
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module I18nLinterRb
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,9 @@
1
+ require "i18n_linter_rb/version"
2
+ require "i18n_linter_rb/linter"
3
+ require "i18n_linter_rb/cli"
4
+
5
+ module I18nLinterRb
6
+ def self.lint(argv = ARGV)
7
+ Cli.new(argv).run
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: i18n-linter-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - MarceloM47
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-08-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.13'
27
+ description: Detects missing translation keys across locale files. Works out of the
28
+ box with Rails config/locales and supports custom paths.
29
+ email:
30
+ executables:
31
+ - i18n-linter-rb
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - exe/i18n-linter-rb
36
+ - lib/i18n_linter_rb.rb
37
+ - lib/i18n_linter_rb/cli.rb
38
+ - lib/i18n_linter_rb/linter.rb
39
+ - lib/i18n_linter_rb/version.rb
40
+ homepage: https://github.com/marcelom47/i18n-linter-rb
41
+ licenses:
42
+ - MIT
43
+ metadata:
44
+ homepage_uri: https://github.com/marcelom47/i18n-linter-rb
45
+ source_code_uri: https://github.com/marcelom47/i18n-linter-rb
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.2.15
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Linter for YAML i18n locale files, built for Ruby and Rails projects
65
+ test_files: []