i18n_add 0.2.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: 2b12a3f21b51efeaa680e5840239fb6026c61d4d0f9ffe786e8e5160e0f0f3c3
4
+ data.tar.gz: 856de3b24b775ba1c60c3251f86de82f36569b6ac4252b74f848108b5a0d4dc2
5
+ SHA512:
6
+ metadata.gz: 8fd16a4033cb63a53a954095f7fbe941f2ea3219f80889652b3ee96b334f1eb24df3af200ac54938e946e557028c86e1f193e45e0662f89cb31757107b8b920e
7
+ data.tar.gz: 559a9709997cf81eed47352104e0eaaf4f1606814e286e8055048ca83f8ecc2b05591b22f008a2f22443af543a88a317e7c90394a7eeee34270bd69cb8a10d3b
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.2.0] - 2025-07-03
4
+
5
+ - Add comprehensive GitHub repository configuration
6
+ - Add CI/CD workflows for automated testing across multiple Ruby versions
7
+ - Add automated security auditing with bundle-audit
8
+ - Add GitHub issue and pull request templates
9
+ - Add automated release workflow triggered by git tags
10
+ - Add repository badges for gem version, CI status, and code style
11
+ - Add security policy (SECURITY.md)
12
+ - Improve project discoverability and community engagement
13
+
14
+ ## [0.1.1] - 2025-07-03
15
+
16
+ - Add contributing guidelines and development documentation
17
+ - Add version management Rake tasks
18
+ - Document conventional commit format and release process
19
+
20
+ ## [0.1.0] - 2025-07-03
21
+
22
+ - Initial release
23
+ - Command-line tool for adding/updating i18n translations in YAML files
24
+ - Support for nested keys and multiple locales
25
+ - Custom file path templates with %{locale} placeholder
26
+ - Efficient in-place YAML file updates
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,106 @@
1
+ # Contributing to I18nAdd
2
+
3
+ ## Development Workflow
4
+
5
+ ### Setting up for development
6
+
7
+ ```bash
8
+ git clone https://github.com/theExtraTerrestrial/i18n_add.git
9
+ cd i18n_add
10
+ bundle install
11
+ ```
12
+
13
+ ### Making Changes
14
+
15
+ 1. Create a feature branch:
16
+ ```bash
17
+ git checkout -b feature/your-feature-name
18
+ ```
19
+
20
+ 2. Make your changes and add tests if applicable
21
+
22
+ 3. Run tests and linting:
23
+ ```bash
24
+ bundle exec rake
25
+ ```
26
+
27
+ 4. Commit using conventional commit format:
28
+ ```bash
29
+ git commit -m "feat: add new feature description"
30
+ ```
31
+
32
+ ### Commit Message Convention
33
+
34
+ We follow [Conventional Commits](https://www.conventionalcommits.org/):
35
+
36
+ - `feat`: New features
37
+ - `fix`: Bug fixes
38
+ - `docs`: Documentation changes
39
+ - `style`: Code style changes (formatting, etc.)
40
+ - `refactor`: Code refactoring
41
+ - `test`: Adding or updating tests
42
+ - `chore`: Maintenance tasks (version bumps, dependency updates)
43
+
44
+ Examples:
45
+ ```
46
+ feat: add support for custom locale patterns
47
+ fix: handle empty YAML files correctly
48
+ docs: update README with new examples
49
+ chore: bump version to 0.2.0
50
+ ```
51
+
52
+ ### Versioning
53
+
54
+ We use [Semantic Versioning](https://semver.org/):
55
+
56
+ - **MAJOR**: Breaking changes
57
+ - **MINOR**: New features (backward compatible)
58
+ - **PATCH**: Bug fixes (backward compatible)
59
+
60
+ #### Version Management
61
+
62
+ Use Rake tasks to bump versions:
63
+
64
+ ```bash
65
+ # Check current version
66
+ bundle exec rake version:show
67
+
68
+ # Bump patch version (0.1.0 -> 0.1.1)
69
+ bundle exec rake version:patch
70
+
71
+ # Bump minor version (0.1.0 -> 0.2.0)
72
+ bundle exec rake version:minor
73
+
74
+ # Bump major version (0.1.0 -> 1.0.0)
75
+ bundle exec rake version:major
76
+ ```
77
+
78
+ #### Release Process
79
+
80
+ 1. Update `CHANGELOG.md` with new changes
81
+ 2. Bump version: `bundle exec rake version:patch` (or minor/major)
82
+ 3. Commit changes: `git add -A && git commit -m "chore: bump version to X.Y.Z"`
83
+ 4. Tag the release: `git tag -a vX.Y.Z -m "Release version X.Y.Z"`
84
+ 5. Push changes and tags: `git push && git push --tags`
85
+ 6. Build and publish gem: `gem build i18n_add.gemspec && gem push i18n_add-X.Y.Z.gem`
86
+
87
+ ### Pull Request Process
88
+
89
+ 1. Ensure your branch is up to date with master:
90
+ ```bash
91
+ git checkout master
92
+ git pull origin master
93
+ git checkout your-feature-branch
94
+ git rebase master
95
+ ```
96
+
97
+ 2. Push your branch and create a pull request
98
+
99
+ 3. Ensure CI passes and request review
100
+
101
+ ## Code Style
102
+
103
+ - Follow Ruby community standards
104
+ - Use RuboCop for linting: `bundle exec rubocop`
105
+ - Write clear, descriptive commit messages
106
+ - Add tests for new features
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in i18n_add.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rubocop", "~> 1.21"
data/Gemfile.lock ADDED
@@ -0,0 +1,50 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ i18n_add (0.2.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.3)
10
+ json (2.12.2)
11
+ language_server-protocol (3.17.0.5)
12
+ lint_roller (1.1.0)
13
+ parallel (1.27.0)
14
+ parser (3.3.8.0)
15
+ ast (~> 2.4.1)
16
+ racc
17
+ prism (1.4.0)
18
+ racc (1.8.1)
19
+ rainbow (3.1.1)
20
+ rake (13.3.0)
21
+ regexp_parser (2.10.0)
22
+ rubocop (1.77.0)
23
+ json (~> 2.3)
24
+ language_server-protocol (~> 3.17.0.2)
25
+ lint_roller (~> 1.1.0)
26
+ parallel (~> 1.10)
27
+ parser (>= 3.3.0.2)
28
+ rainbow (>= 2.2.2, < 4.0)
29
+ regexp_parser (>= 2.9.3, < 3.0)
30
+ rubocop-ast (>= 1.45.1, < 2.0)
31
+ ruby-progressbar (~> 1.7)
32
+ unicode-display_width (>= 2.4.0, < 4.0)
33
+ rubocop-ast (1.45.1)
34
+ parser (>= 3.3.7.2)
35
+ prism (~> 1.4)
36
+ ruby-progressbar (1.13.0)
37
+ unicode-display_width (3.1.4)
38
+ unicode-emoji (~> 4.0, >= 4.0.4)
39
+ unicode-emoji (4.0.4)
40
+
41
+ PLATFORMS
42
+ x86_64-linux
43
+
44
+ DEPENDENCIES
45
+ i18n_add!
46
+ rake (~> 13.0)
47
+ rubocop (~> 1.21)
48
+
49
+ BUNDLED WITH
50
+ 2.4.10
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 theExtraTerrestrial
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # I18nAdd
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/i18n_add.svg)](https://badge.fury.io/rb/i18n_add)
4
+ [![CI](https://github.com/theExtraTerrestrial/i18n_add/workflows/CI/badge.svg)](https://github.com/theExtraTerrestrial/i18n_add/actions?query=workflow%3ACI)
5
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop)
6
+
7
+ A command-line tool for adding and updating internationalization (i18n) translations in YAML files efficiently. Supports nested keys, multiple locales, and custom file paths.
8
+
9
+ ## Installation
10
+
11
+ Install the gem by executing:
12
+
13
+ $ gem install i18n_add
14
+
15
+ Or add it to your application's Gemfile:
16
+
17
+ gem 'i18n_add'
18
+
19
+ And then execute:
20
+
21
+ $ bundle install
22
+
23
+ ## Usage
24
+
25
+ Add or update translations using the command line:
26
+
27
+ ```bash
28
+ # Add a single translation
29
+ i18n_add en.welcome.message="Welcome to our app"
30
+
31
+ # Add multiple translations
32
+ i18n_add -t en.welcome.message="Welcome" -t es.welcome.message="Bienvenido"
33
+
34
+ # Use custom file path template
35
+ i18n_add -f "locales/%{locale}.yml" en.greeting="Hello"
36
+ ```
37
+
38
+ ### Command Options
39
+
40
+ - `-t, --translation TRANSLATION`: Translation entry (can be used multiple times). Format: `locale.dot.separated.key=value`
41
+ - `-f, --file FILE`: Custom file path template. Use `%{locale}` as placeholder. Default: `config/locales/%{locale}/main.%{locale}.yml`
42
+ - `-h, --help`: Show help message
43
+
44
+ ### Examples
45
+
46
+ ```bash
47
+ # Basic usage
48
+ i18n_add en.users.welcome="Welcome user"
49
+
50
+ # Multiple translations
51
+ i18n_add -t en.buttons.save="Save" -t en.buttons.cancel="Cancel" -t es.buttons.save="Guardar"
52
+
53
+ # Custom file structure
54
+ i18n_add -f "app/locales/%{locale}.yml" en.nav.home="Home"
55
+
56
+ # Nested keys
57
+ i18n_add en.forms.user.name="Full Name" en.forms.user.email="Email Address"
58
+ ```
59
+
60
+ ## Development
61
+
62
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
63
+
64
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
65
+
66
+ ## Contributing
67
+
68
+ Bug reports and pull requests are welcome on GitHub at https://github.com/theExtraTerrestrial/i18n_add.
69
+
70
+ ## License
71
+
72
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
9
+
10
+ # Version management tasks
11
+ namespace :version do
12
+ desc "Show current version"
13
+ task :show do
14
+ require_relative "lib/i18n_add/version"
15
+ puts "Current version: #{I18nAdd::VERSION}"
16
+ end
17
+
18
+ desc "Bump patch version"
19
+ task :patch do
20
+ bump_version(:patch)
21
+ end
22
+
23
+ desc "Bump minor version"
24
+ task :minor do
25
+ bump_version(:minor)
26
+ end
27
+
28
+ desc "Bump major version"
29
+ task :major do
30
+ bump_version(:major)
31
+ end
32
+ end
33
+
34
+ def bump_version(type)
35
+ require_relative "lib/i18n_add/version"
36
+ current = I18nAdd::VERSION
37
+ major, minor, patch = current.split('.').map(&:to_i)
38
+
39
+ case type
40
+ when :patch
41
+ patch += 1
42
+ when :minor
43
+ minor += 1
44
+ patch = 0
45
+ when :major
46
+ major += 1
47
+ minor = 0
48
+ patch = 0
49
+ end
50
+
51
+ new_version = "#{major}.#{minor}.#{patch}"
52
+
53
+ # Update version file
54
+ version_file = "lib/i18n_add/version.rb"
55
+ content = File.read(version_file)
56
+ content.gsub!(/"#{current}"/, "\"#{new_version}\"")
57
+ File.write(version_file, content)
58
+
59
+ puts "Version bumped from #{current} to #{new_version}"
60
+ puts "Don't forget to:"
61
+ puts "1. Update CHANGELOG.md"
62
+ puts "2. Commit changes: git add -A && git commit -m 'chore: bump version to #{new_version}'"
63
+ puts "3. Tag release: git tag -a v#{new_version} -m 'Release version #{new_version}'"
64
+ puts "4. Push: git push && git push --tags"
65
+ end
data/SECURITY.md ADDED
@@ -0,0 +1,28 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 0.1.x | :white_check_mark: |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ If you discover a security vulnerability within I18nAdd, please send an email to erhards.timanis@miittech.lv. All security vulnerabilities will be promptly addressed.
12
+
13
+ Please do not disclose security-related issues publicly until they have been investigated and resolved.
14
+
15
+ ### What to include in your report
16
+
17
+ - Description of the vulnerability
18
+ - Steps to reproduce the issue
19
+ - Potential impact
20
+ - Any suggested fixes (if you have them)
21
+
22
+ ### Response timeline
23
+
24
+ - Initial response: Within 48 hours
25
+ - Status update: Within 7 days
26
+ - Resolution: Depends on complexity, but we aim for 30 days maximum
27
+
28
+ Thank you for helping keep I18nAdd secure!
@@ -0,0 +1,5 @@
1
+ en:
2
+ welcome:
3
+ message: Hello World
4
+ buttons:
5
+ save: Save
@@ -0,0 +1,3 @@
1
+ es:
2
+ buttons:
3
+ save: Guardar
data/exe/i18n_add ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/i18n_add"
5
+
6
+ I18nAdd::CLI.run(ARGV)
data/i18n_add.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/i18n_add/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "i18n_add"
7
+ spec.version = I18nAdd::VERSION
8
+ spec.authors = ["theExtraTerrestrial"]
9
+ spec.email = ["erhards.timanis@miittech.lv"]
10
+
11
+ spec.summary = "Add or update multiple translations in locale YAML files efficiently"
12
+ spec.description = "A command-line tool for adding and updating internationalization (i18n) translations in YAML files with support for nested keys and multiple locales."
13
+ spec.homepage = "https://github.com/theExtraTerrestrial/i18n_add"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/theExtraTerrestrial/i18n_add"
21
+ spec.metadata["changelog_uri"] = "https://github.com/theExtraTerrestrial/i18n_add/blob/main/CHANGELOG.md"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
28
+ end
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ # Uncomment to register a new dependency of your gem
35
+ # spec.add_dependency "example-gem", "~> 1.0"
36
+
37
+ # For more information and examples about making a new gem, check out our
38
+ # guide at: https://bundler.io/guides/creating_gem.html
39
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'yaml'
5
+ require 'fileutils'
6
+ require_relative 'yaml_processor'
7
+
8
+ module I18nAdd
9
+ class CLI
10
+ def self.run(args = ARGV)
11
+ new.run(args)
12
+ end
13
+
14
+ def run(args)
15
+ help_msg = <<~HELP
16
+ Usage: i18n_add [OPTIONS] [-t <locale.key=value> ...] locale.key=value
17
+ -t, --translation TRANSLATION [Optional] Translation entry, can be specified multiple times. Format: locale.dot.separated.key=value
18
+ -f, --file FILE [Optional] File path. If not specified, defaults to 'config/locales/%{locale}/main.%{locale}.yml' for each locale.
19
+ -h, --help Show this help message and exit.
20
+ HELP
21
+
22
+ if args.first == 'help' || args.empty? || args.include?('-h') || args.include?('--help')
23
+ puts help_msg
24
+ return
25
+ end
26
+
27
+ options = { translations: [] }
28
+ OptionParser.new do |opts|
29
+ opts.banner = "Usage: i18n_add help or i18n_add [options]"
30
+ opts.on('-tVAL', '--translation VAL', '[Optional] Translation entry, can be specified multiple times. Format: locale.dot.separated.key=value') { |v| options[:translations] << v }
31
+ opts.on('-f', '--file FILE', '[Optional] File path. Supports %{locale} as a placeholder for the locale. If not specified, defaults to \'config/locales/%{locale}/main.%{locale}.yml\' for each locale.') { |v| options[:file] = v }
32
+ opts.on('-h', '--help', 'Show this help message and exit.') do
33
+ puts help_msg
34
+ return
35
+ end
36
+ end.parse!(args)
37
+
38
+ # If no -t given, but a single positional argument remains, treat it as a translation
39
+ if options[:translations].empty? && args.size == 1 && args[0] =~ /^[a-z]{2}\..+?=.*/
40
+ options[:translations] << args.shift
41
+ end
42
+
43
+ file_arg = options[:file]
44
+ translations = options[:translations]
45
+
46
+ if translations.empty?
47
+ puts "\e[31mNo translations provided. Use -t or --translation.\e[0m"
48
+ exit 1
49
+ end
50
+
51
+ # Parse all entries and group by file_path
52
+ file_map = {}
53
+ translations.each do |entry|
54
+ if entry =~ /^([a-z]{2})\.(.+?)=(.+)$/
55
+ locale, key_path, value = $1, $2, $3
56
+ # Support only %{locale} in file_arg as a template
57
+ if file_arg
58
+ file_path = file_arg.gsub('%{locale}', locale)
59
+ else
60
+ file_path = "config/locales/#{locale}/main.#{locale}.yml"
61
+ end
62
+ file_map[file_path] ||= { locale: locale, entries: [] }
63
+ file_map[file_path][:entries] << { key_path: key_path, value: value }
64
+ else
65
+ puts "\e[31mInvalid translation format: #{entry}\e[0m"
66
+ end
67
+ end
68
+
69
+ processor = YamlProcessor.new
70
+ processor.process_files(file_map)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nAdd
4
+ VERSION = "0.2.0"
5
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nAdd
4
+ class YamlProcessor
5
+ def process_files(file_map)
6
+ file_map.each do |file_path, info|
7
+ locale = info[:locale]
8
+ FileUtils.mkdir_p(File.dirname(file_path))
9
+ lines = File.exist?(file_path) ? File.read(file_path, encoding: 'UTF-8').lines : ["#{locale}:\n"]
10
+ changed = false
11
+
12
+ info[:entries].each do |e|
13
+ keys = e[:key_path].split('.')
14
+ value = e[:value]
15
+ parent_line = nil
16
+ parent_level = 0
17
+ found = false
18
+
19
+ # Find the deepest parent that exists
20
+ cur_level = 1
21
+ lines.each_with_index do |line, idx|
22
+ next unless line =~ /^\s*(\w+):/ # only YAML key lines
23
+ lkey = line.strip.split(':', 2)[0]
24
+ lindent = line[/^ */].size / 2
25
+ if lkey == keys[cur_level - 1] && lindent == cur_level
26
+ parent_line = idx
27
+ parent_level = cur_level
28
+ cur_level += 1
29
+ if cur_level - 1 == keys.size
30
+ # Found the full key, update value
31
+ val_line = idx
32
+ # If next line is indented more, it's a block
33
+ if lines[val_line + 1] && lines[val_line + 1] =~ /^#{yaml_indent(cur_level + 1)}/
34
+ # Remove all block lines
35
+ block_end = val_line + 1
36
+ while block_end < lines.size && lines[block_end] =~ /^#{yaml_indent(cur_level + 1)}/
37
+ block_end += 1
38
+ end
39
+ lines.slice!(val_line + 1...block_end)
40
+ end
41
+ # Replace value
42
+ lines[val_line] = "#{yaml_indent(cur_level)}#{keys.last}: #{yaml_escape_value(value)}\n"
43
+ changed = true
44
+ found = true
45
+ puts "\e[32m✓ Updated #{locale}.#{e[:key_path]} in #{file_path}\e[0m"
46
+ break
47
+ end
48
+ end
49
+ end
50
+
51
+ next if found
52
+
53
+ # Insert missing parents and key at the end
54
+ insert_idx = lines.size
55
+ # Try to find the last line of the deepest parent
56
+ if parent_line
57
+ # Find last line at this or deeper indent
58
+ insert_idx = parent_line + 1
59
+ while insert_idx < lines.size && (lines[insert_idx] =~ /^#{yaml_indent(parent_level + 1)}/ || lines[insert_idx].strip.empty?)
60
+ insert_idx += 1
61
+ end
62
+ end
63
+
64
+ # Build missing parents and key
65
+ missing = keys[parent_level..-2] || []
66
+ frag = +"" # Make string mutable
67
+ cur_indent = parent_level + 1
68
+ missing.each do |k|
69
+ frag << "#{yaml_indent(cur_indent)}#{k}:\n"
70
+ cur_indent += 1
71
+ end
72
+ frag << "#{yaml_indent(cur_indent)}#{keys.last}: #{yaml_escape_value(value)}\n"
73
+ lines.insert(insert_idx, frag)
74
+ changed = true
75
+ puts "\e[32m✓ Inserted #{locale}.#{e[:key_path]} in #{file_path}\e[0m"
76
+ end
77
+
78
+ if changed
79
+ File.open(file_path, 'w:utf-8') { |f| f.write(lines.join) }
80
+ puts "\e[32m✓ Updated #{file_path}\e[0m"
81
+ else
82
+ puts "No changes needed for #{file_path}"
83
+ end
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def yaml_indent(level)
90
+ ' ' * level
91
+ end
92
+
93
+ def yaml_escape_value(val)
94
+ if val.include?("\n")
95
+ # Multiline block
96
+ "|\n" + val.split("\n").map { |l| " " + l }.join("\n")
97
+ else
98
+ # Single line, quote if needed
99
+ val =~ /[\":{}\[\],#&*!|>'%@`]/ ? '"' + val.gsub('"', '\\"') + '"' : val
100
+ end
101
+ end
102
+ end
103
+ end
data/lib/i18n_add.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "i18n_add/version"
4
+ require_relative "i18n_add/cli"
5
+
6
+ module I18nAdd
7
+ class Error < StandardError; end
8
+ end
data/sig/i18n_add.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module I18nAdd
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: i18n_add
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - theExtraTerrestrial
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-07-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A command-line tool for adding and updating internationalization (i18n)
14
+ translations in YAML files with support for nested keys and multiple locales.
15
+ email:
16
+ - erhards.timanis@miittech.lv
17
+ executables:
18
+ - i18n_add
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".rubocop.yml"
23
+ - CHANGELOG.md
24
+ - CONTRIBUTING.md
25
+ - Gemfile
26
+ - Gemfile.lock
27
+ - LICENSE.txt
28
+ - README.md
29
+ - Rakefile
30
+ - SECURITY.md
31
+ - config/locales/en/main.en.yml
32
+ - config/locales/es/main.es.yml
33
+ - exe/i18n_add
34
+ - i18n_add.gemspec
35
+ - lib/i18n_add.rb
36
+ - lib/i18n_add/cli.rb
37
+ - lib/i18n_add/version.rb
38
+ - lib/i18n_add/yaml_processor.rb
39
+ - sig/i18n_add.rbs
40
+ homepage: https://github.com/theExtraTerrestrial/i18n_add
41
+ licenses:
42
+ - MIT
43
+ metadata:
44
+ allowed_push_host: https://rubygems.org
45
+ homepage_uri: https://github.com/theExtraTerrestrial/i18n_add
46
+ source_code_uri: https://github.com/theExtraTerrestrial/i18n_add
47
+ changelog_uri: https://github.com/theExtraTerrestrial/i18n_add/blob/main/CHANGELOG.md
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 2.6.0
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.2.33
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Add or update multiple translations in locale YAML files efficiently
67
+ test_files: []