Skip to content

Conversation

@justin808
Copy link
Member

@justin808 justin808 commented Nov 22, 2025

Summary

Makes the react_on_rails:locale rake task idempotent - it now skips generation when locale files are already up-to-date. This eliminates duplicate work when the task is called multiple times (e.g., in Shakapacker's precompile_hook).

Key Changes

Idempotent Generation Logic

  • Added timestamp checking to skip generation when output files are newer than source YAML files
  • Returns early with message: "Locale files are up to date, skipping generation"
  • Prevents unnecessary rebuilds during development and CI

Force Regeneration Option

  • Added force=true parameter to override timestamp checking
  • Usage: rake react_on_rails:locale force=true
  • Useful when you need to force regeneration regardless of timestamps

RBS Type Signatures

  • Added comprehensive type signatures in sig/react_on_rails/locales.rbs
  • Covers Locales::Base, Locales::ToJs, and Locales::ToJson classes
  • Improves type safety for the locale generation system

User Feedback

  • Prints confirmation message when files are generated
  • Prints skip message when files are up-to-date
  • Clear messaging improves debugging experience

Use Cases

Safe for Shakapacker's precompile_hook:

# config/shakapacker.yml
default: &default
  precompile_hook: 'bundle exec rake react_on_rails:locale'

Now safe to call multiple times without performance penalty - subsequent calls skip instantly.

Development workflow:

bundle exec rake react_on_rails:locale  # First call generates files
bundle exec rake react_on_rails:locale  # Subsequent calls skip if up-to-date
bundle exec rake react_on_rails:locale force=true  # Force regeneration

Implementation Details

Timestamp Comparison:

  • Compares mtime of YAML source files against output files
  • Considers outdated if: no output files exist OR any YAML file is newer than oldest output file
  • Edge case handled: empty locale_files array returns true (outdated)

Force Parameter Flow:

  • Passed from rake task → Locales.compile(force:)Base.new(force:)
  • Bypasses timestamp check when force: true
  • Defaults to false for backward compatibility

Testing

  • ✅ Added tests for force parameter passing through compile chain
  • ✅ Added test for force regeneration with future timestamps
  • ✅ Existing tests verify timestamp-based skipping behavior

Documentation

Updated docs/building-features/i18n.md to explain:

  • Idempotent behavior and benefits
  • Usage with Shakapacker's precompile_hook
  • Force flag usage examples

Breaking Changes

None - idempotent behavior is backward compatible.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Locale generation task is idempotent and will skip work when files are up-to-date.
    • Added a force=true option to bypass timestamp checks and force regeneration.
    • Task now emits clear notices when skipping or generating locale files.
  • Documentation

    • Updated i18n guidance with examples, auto-run (precompile hook) configuration, and force usage.
  • Tests

    • Added tests verifying force=true forces regeneration and is propagated through the compile flow.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 22, 2025

Walkthrough

Adds idempotent locale generation for the react_on_rails:locale rake task with a force option. The compile pipeline (module, Base, ToJs/ToJson) accepts force: and skips generation when locale files are up-to-date unless forced; documentation, signatures, rake task, and tests updated.

Changes

Cohort / File(s) Summary
Documentation & Changelog
CHANGELOG.md, docs/building-features/i18n.md
Documented idempotent locale generation, examples for normal and forced runs, and updated guidance for Shakapacker precompile_hook configuration.
Core Implementation
lib/react_on_rails/locales/base.rb, lib/tasks/locale.rake
Added force: keyword (default false) to ReactOnRails::Locales.compile; Base and builders accept force: and skip generation when files are up-to-date unless forced; rake task parses ENV["force"] and passes boolean to compile.
Type Signatures
sig/react_on_rails/locales.rbs
Added/updated signatures for ReactOnRails::Locales.compile(force: bool = false), Base#initialize(force: bool = false), and declared ToJs/ToJson subclasses.
Tests
spec/react_on_rails/locales_spec.rb, spec/react_on_rails/locales_to_js_spec.rb
Added tests to ensure force: true propagates to ToJson/ToJs and that forced regeneration updates file mtimes even when files are up-to-date.

Sequence Diagram(s)

sequenceDiagram
    participant R as Rake Task
    participant M as ReactOnRails::Locales
    participant B as Base
    participant G as ToJs/ToJson

    R->>M: compile(force: true/false)
    activate M
    M->>B: new(force: force)
    activate B
    alt force == true
        B->>G: new(force: true)
        activate G
        G->>G: generate locale files (ignore timestamps)
        deactivate G
    else force == false
        alt files outdated or locale_files empty
            B->>G: new(force: false)
            activate G
            G->>G: generate locale files
            deactivate G
        else files up-to-date
            B-->>M: emit notice (skip generation)
        end
    end
    deactivate B
    M-->>R: success/notice
    deactivate M
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect lib/react_on_rails/locales/base.rb for conditional generation and empty-locale-files handling.
  • Verify boolean parsing of ENV["force"] in lib/tasks/locale.rake.
  • Confirm tests assert mtime updates and force: propagation.
  • Check sig/react_on_rails/locales.rbs aligns with runtime signatures.

Possibly related issues

  • #2090 — Implements idempotent locale generation with timestamp checks and a force=true option; behavior matches this PR.

Possibly related PRs

  • #2092 — Makes equivalent changes to locale-generation APIs and rake task to add idempotent generation and force=true; closely related.

Suggested reviewers

  • AbanoubGhadban
  • Romex91

Poem

🐇 I nudged the timestamps, gave files a little blink,
Locales now wait quiet till something makes them wink.
Whisper force=true and I’ll scurry, fresh output in hand—
Then off to the clover I hop, tidy and grand. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: making locale generation idempotent with a force flag support, which aligns with the core objective of the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jg-/idempotent-locale-generation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Nov 22, 2025

Code Review for PR #2093

Great work on making locale generation idempotent! This is a solid improvement that will prevent duplicate work and make the task safer to use in hooks. Here's my detailed feedback:


✅ Strengths

1. Excellent Implementation of Idempotency

The timestamp-based approach is clean and efficient:

  • Properly compares YAML source mtimes against generated file mtimes
  • Handles edge cases (empty files, missing files) correctly
  • Early return pattern avoids unnecessary work

2. Well-Designed Force Flag

  • Clean parameter flow: rake task → compile() → Base.new()
  • Accepts multiple true values (true, 1, yes) for flexibility
  • Properly documented in task description
  • Backward compatible (defaults to false)

3. Comprehensive Test Coverage

  • Tests force parameter propagation through both ToJs and ToJson paths
  • Tests force regeneration with future timestamps (edge case)
  • Existing timestamp-based tests ensure idempotency works

4. Type Safety with RBS

Added excellent type signatures covering:

  • All public and private methods
  • Proper return types for Base, ToJs, ToJson classes
  • This improves maintainability and catches type errors early

5. Great Documentation

  • Updated i18n.md with clear examples and use cases
  • Explains Shakapacker precompile_hook integration (the primary use case)
  • Provides both manual coordination and automated approaches

🐛 Issues Found

1. CRITICAL: Changelog PR Number Mismatch

CHANGELOG.md:28
- [PR 2092](https://github.com/shakacode/react_on_rails/pull/2092)

Issue: The changelog references PR #2092, but this is PR #2093.

Fix: Update to [PR 2093](https://github.com/shakacode/react_on_rails/pull/2093)

2. Potential Bug: Empty locale_files Array

lib/react_on_rails/locales/base.rb:67-72
def files_are_outdated
  return true if locale_files.empty?

  latest_yml = locale_files.map { |file| File.mtime(file) }.max
  earliest = exist_files.map { |file| File.mtime(file) }.min
  latest_yml > earliest
end

Issue: If exist_files is empty (which is already checked in obsolete?), the .min call will return nil, causing latest_yml > nil which always returns false in Ruby.

Current flow:

  1. obsolete? returns true if exist_files.empty? (line 57) ✅
  2. If execution reaches files_are_outdated, we know exist_files is NOT empty
  3. BUT: the check at line 67 (locale_files.empty?) seems defensive

Analysis: This is actually safe because obsolete? short-circuits when exist_files.empty?, so line 70 (.min) will never see an empty array. However, the defensive check at line 67 suggests there might be cases where locale_files could be empty during normal operation.

Recommendation: This code is correct but could be clearer. Consider adding a comment explaining the assumption:

def files_are_outdated
  # Defensive check: in theory locale_files should never be empty here
  # because the rake task validates i18n_yml_dir exists, but guard anyway
  return true if locale_files.empty?

  latest_yml = locale_files.map { |file| File.mtime(file) }.max
  # Safe to call .min because obsolete? already checked exist_files.empty?
  earliest = exist_files.map { |file| File.mtime(file) }.min
  latest_yml > earliest
end

💡 Suggestions for Improvement

1. User Feedback Messages Could Be More Actionable

lib/react_on_rails/locales/base.rb:43
puts "Locale files are up to date, skipping generation. Use force=true to regenerate."

Suggestion: Make the force flag usage more explicit:

puts "Locale files are up to date, skipping generation."
puts "To force regeneration, run: rake react_on_rails:locale force=true"

2. RBS: Consider More Specific Types

sig/react_on_rails/locales.rbs:18
def locale_files: () -> Array[String]

Current: Returns Array[String] but implementation shows it can return nil in some cases (line 93 of base.rb shows .presence which can return nil).

Suggestion: Update signature to match reality:

def locale_files: () -> (Array[String] | nil)

However, I noticed the code does handle nil properly (line 87-93), so this might be intentional. If locale_files is guaranteed to never return nil due to the validation in compile, the current signature is fine.

3. Test: Missing Edge Case

The tests cover:

  • ✅ Obsolete files (updated)
  • ✅ Up-to-date files (skipped)
  • ✅ Force regeneration with future timestamps

Missing:

  • What happens when locale_files is empty? (Currently returns true from line 67)
  • What happens when output directory doesn't exist?

These are likely covered by integration tests, but unit tests would make the contract clearer.


🔒 Security

No security concerns. The force flag is only accessible via ENV variable in a rake task context, which is appropriate for this use case.


⚡ Performance

Excellent performance characteristics:

  • Timestamp checks are O(n) where n = number of files (very fast)
  • Skip is instant (no file I/O beyond stat calls)
  • Force flag provides escape hatch when needed

Note: The puts statements will show in production logs during asset precompilation. This is good for debugging but consider whether you want to use Rails.logger.info instead for more control over log levels.


📋 Checklist

Before merging:

  • Fix changelog PR number (2092 → 2093)
  • Consider adding clarifying comments to files_are_outdated
  • Verify RBS signatures match implementation (especially locale_files return type)
  • Run bundle exec rake rbs:validate to verify RBS signatures
  • Run bundle exec rubocop to ensure no linting issues (as per CLAUDE.md)

🎯 Overall Assessment

Verdict: ✅ Approve with minor fix required

This PR successfully implements idempotent locale generation with excellent test coverage and documentation. The only blocking issue is the changelog PR number mismatch. The other suggestions are optional improvements that can be addressed in follow-up work or during merge.

Great job on:

  • Clean, maintainable code
  • Comprehensive RBS type signatures
  • Thorough documentation
  • Well-designed force flag API
  • Backward compatibility

Recommendation: Fix the changelog PR number and merge! 🚀

justin808 added a commit that referenced this pull request Nov 22, 2025
References the idempotent locale generation feature (PR #2093) and
explains how bin/dev automatically coordinates the precompile hook.
@claude
Copy link

claude bot commented Nov 22, 2025

Code Review: Idempotent Locale Generation

✅ Overall Assessment

This is a well-designed and well-tested feature that addresses a real pain point (#2090). The implementation is solid with good attention to detail. The PR demonstrates excellent practices: comprehensive testing, RBS type signatures, clear documentation, and backward compatibility.


Strengths

1. Excellent Design Decisions

  • Idempotent by default: Smart choice making the timestamp check the default behavior
  • Force flag override: Provides escape hatch when needed
  • Backward compatible: Existing code continues to work without changes
  • Clear user feedback: Informative messages help with debugging

2. Comprehensive Testing

  • ✅ Force parameter propagation tests (compile → ToJson/ToJs)
  • ✅ Force flag bypass test with future timestamps
  • ✅ Existing tests verify idempotent behavior
  • ✅ Good use of shared examples for reusable test patterns

3. Type Safety

  • ✅ Complete RBS type signatures for the Locales module
  • ✅ Proper optional parameter typing (?force: bool)
  • ✅ Well-structured signatures covering private methods

4. Documentation

  • ✅ Clear documentation in docs/building-features/i18n.md
  • ✅ Recommended pattern using Shakapacker's precompile_hook
  • ✅ Force flag usage examples
  • ✅ Updated CHANGELOG with user-facing improvements

Potential Issues & Suggestions

🟡 Minor: Edge Case in files_are_outdated

Location: lib/react_on_rails/locales/base.rb:64-69

The guard clause for empty locale_files was added in the second commit:

def files_are_outdated
  return true if locale_files.empty?  # Added to prevent nil comparison
  
  latest_yml = locale_files.map { |file| File.mtime(file) }.max
  earliest = exist_files.map { |file| File.mtime(file) }.min
  latest_yml > earliest
end

Concern: If locale_files is empty, the method returns true (outdated), which triggers generation. However, generate_translations will iterate over an empty array, producing empty translations.

Question: Is this the intended behavior? Should it:

  1. Return true and generate empty files (current behavior)
  2. Return false to skip generation when there are no source files
  3. Raise an error indicating misconfiguration

Recommendation: Consider adding a test case for this scenario and documenting the expected behavior.


🟡 Minor: ENV Variable Parsing Flexibility

Location: lib/tasks/locale.rake:18

force = %w[true 1 yes].include?(ENV["force"]&.downcase)

Observation: The flexible parsing (true|1|yes) is user-friendly and well-implemented with &.downcase for safety.

Suggestion: Consider documenting the accepted values in the task description:

Options:
  force=true|1|yes - Force regeneration even if files are up to date
                      Example: rake react_on_rails:locale force=true

🟡 Minor: Print Statements vs Logger

Location: lib/react_on_rails/locales/base.rb:43, 49

puts "Locale files are up to date, skipping generation..."
puts "Generated locale files in #{i18n_dir}"

Observation: Using puts is fine for rake tasks, but consider:

  • These messages appear even when called programmatically (not just from rake task)
  • No way to suppress output in tests or automated contexts

Suggestion: Consider one of:

  1. Use Rails.logger.info if in Rails context
  2. Make output configurable (e.g., verbose: true parameter)
  3. Keep as-is if the feedback is valuable in all contexts (probably fine for this use case)

🟢 Best Practice: Test Timestamp Handling

Location: spec/react_on_rails/locales_to_js_spec.rb:54-68

The force flag test is well-designed:

# Touch files to make them newer than YAML (up-to-date)
future_time = Time.current + 1.minute
FileUtils.touch(translations_path, mtime: future_time)

# Force regeneration even though files are up-to-date
described_class.new(force: true)

# New mtime should be different from the future_time we set
expect(File.mtime(translations_path)).not_to eq(future_time)

✅ This correctly validates that force: true bypasses the timestamp check.


Security Considerations

No security concerns identified:

  • Force flag is boolean, no injection risk
  • ENV parsing is safe with whitelist approach
  • File operations use existing paths from configuration
  • No user input directly affects file paths

Performance Considerations

Performance improvements achieved:

  • Timestamp checking is O(n) where n = number of locale files (acceptable)
  • Skips expensive file generation when not needed
  • Safe to call multiple times in precompile hooks
  • No performance regressions introduced

Note: The idempotent behavior is the primary performance benefit - subsequent calls return immediately.


CHANGELOG Review

Properly formatted and accurate:

  • Correct PR number (2092 → 2093 after the fix in commit 2)
  • Clear description of user-facing changes
  • Proper attribution format
  • Placed in "Improved" section (appropriate category)

RBS Type Signatures Review

Location: sig/react_on_rails/locales.rbs

Comprehensive and accurate signatures:

  • All public and private methods covered
  • Correct return types (ToJs | ToJson union type for compile)
  • Optional parameters properly typed
  • Private method visibility preserved

Minor observation: The format method signature shows:

def format: (untyped input) -> Symbol

Could be more specific: (String | Symbol input) -> Symbol, but untyped is safe here.


Documentation Review

Location: docs/building-features/i18n.md

Excellent documentation updates:

  • Clear explanation of idempotent behavior
  • Practical examples with code blocks
  • Recommended Shakapacker integration pattern
  • Fallback guidance for alternative approaches

Suggestion: Consider adding a troubleshooting section:

### Troubleshooting

**Locale files not updating after YAML changes?**
- Use `force=true` to bypass timestamp checking
- Check file permissions in output directory
- Verify `config.i18n_dir` is correctly configured

Test Coverage Assessment

Strong test coverage:

  • ✅ Force parameter propagation through call chain
  • ✅ Force flag bypasses timestamp checking
  • ✅ Existing idempotent behavior (shared examples)
  • ✅ Error handling for unsafe YAML

Coverage gap: No test for locale_files.empty? scenario (mentioned above)


Recommendations Summary

  1. Optional: Add test for empty locale_files scenario and document expected behavior
  2. Optional: Document accepted ENV values (true|1|yes) in task description
  3. Optional: Consider adding troubleshooting section to docs
  4. Consider: Whether puts output should be suppressible in programmatic contexts

Final Verdict

Approved - Ready to Merge

This is high-quality work that follows all repository conventions:

  • ✅ RuboCop clean (CI passing)
  • ✅ All tests passing
  • ✅ RBS signatures included
  • ✅ CHANGELOG updated correctly
  • ✅ Documentation comprehensive
  • ✅ Backward compatible
  • ✅ Addresses real user pain point

The suggestions above are minor refinements, not blockers. Great job on this PR! 🎉


References:

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
sig/react_on_rails/locales.rbs (1)

1-46: Loosen flatten key type and optionally mark private helper in RBS

The RBS looks aligned with the Ruby code overall, but two small tweaks would make it more accurate:

  • Base#flatten can yield both String and Symbol keys (e.g., leaf keys stay as strings, nested ones become symbols), so Hash[Symbol, untyped] is narrower than the runtime behavior. Consider widening to Hash[String | Symbol, untyped] or Hash[untyped, untyped] to avoid type-checker surprises.
  • Locales.check_config_directory_exists is a private class method in Ruby; if you rely on visibility in tooling, you may want to declare it in a private section in the RBS as well.
spec/react_on_rails/locales_to_js_spec.rb (1)

54-69: Good coverage of forced regeneration; minor note on timestamp resolution

This example nicely proves that force: true regenerates even when files are “up‑to‑date” and that the new mtime advances beyond the initial generation. If you ever see intermittent failures on filesystems with coarse mtime resolution, you could guard against same‑second writes (e.g., by adding a small sleep or by asserting on >= initial_mtime plus != future_time only), but the current assertions are fine for most environments.

lib/react_on_rails/locales/base.rb (1)

7-21: Idempotent/force logic looks correct; note behavior with empty locale sets and logging

The new flow in compile and Base#initialize correctly achieves:

  • Idempotency: when output files exist and are not obsolete, non‑forced runs return early without touching mtimes.
  • Forcing: force: true skips the obsolete? check and always regenerates, matching the rake task’s force flag.

A couple of behavioral notes to be aware of:

  • files_are_outdated now treats locale_files.empty? as “outdated”, so if i18n_yml_dir points to an existing but empty directory you’ll regenerate on every call, producing the same empty JSON/JS each time. That’s safe, but if this is considered a misconfiguration you might eventually want to surface it (e.g., via a clearer warning or config check).
  • The puts messages are great for the rake task UX; if ReactOnRails::Locales::Base is ever used from non‑rake contexts where stdout logging is undesirable, you may want to route these through a logger instead.

Overall, the timestamp comparison (latest_yml vs earliest existing output) and force handling look sound and align with the new specs.

Also applies to: 39-50, 67-72

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2306825 and df8d156.

📒 Files selected for processing (7)
  • CHANGELOG.md (1 hunks)
  • docs/building-features/i18n.md (1 hunks)
  • lib/react_on_rails/locales/base.rb (4 hunks)
  • lib/tasks/locale.rake (1 hunks)
  • sig/react_on_rails/locales.rbs (1 hunks)
  • spec/react_on_rails/locales_spec.rb (1 hunks)
  • spec/react_on_rails/locales_to_js_spec.rb (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • spec/react_on_rails/locales_spec.rb
  • spec/react_on_rails/locales_to_js_spec.rb
  • sig/react_on_rails/locales.rbs
  • lib/react_on_rails/locales/base.rb
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • spec/react_on_rails/locales_spec.rb
  • spec/react_on_rails/locales_to_js_spec.rb
  • CHANGELOG.md
  • docs/building-features/i18n.md
  • sig/react_on_rails/locales.rbs
  • lib/react_on_rails/locales/base.rb
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • spec/react_on_rails/locales_to_js_spec.rb
  • docs/building-features/i18n.md
  • sig/react_on_rails/locales.rbs
  • lib/react_on_rails/locales/base.rb
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • docs/building-features/i18n.md
  • sig/react_on_rails/locales.rbs
  • lib/react_on_rails/locales/base.rb
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • sig/react_on_rails/locales.rbs
📚 Learning: 2025-04-09T12:56:10.756Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1696
File: node_package/src/RSCPayloadContainer.ts:0-0
Timestamp: 2025-04-09T12:56:10.756Z
Learning: In the react_on_rails codebase, RSC payloads are already stringified using `JSON.stringify()` before being processed by the `escapeScript` function, which handles escaping of special characters. The function only needs to handle specific HTML markers like comments and closing script tags.

Applied to files:

  • sig/react_on_rails/locales.rbs
  • lib/react_on_rails/locales/base.rb
🧬 Code graph analysis (2)
spec/react_on_rails/locales_spec.rb (2)
lib/react_on_rails/configuration.rb (1)
  • configuration (33-71)
lib/react_on_rails/locales/base.rb (1)
  • compile (7-22)
lib/react_on_rails/locales/base.rb (1)
lib/react_on_rails/configuration.rb (1)
  • initialize (134-190)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: dummy-app-integration-tests (3.4, 22, latest)
  • GitHub Check: examples (3.4, latest)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: pro-lint-js-and-ruby
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build
  • GitHub Check: claude-review
🔇 Additional comments (3)
docs/building-features/i18n.md (1)

24-51: Docs clearly describe idempotent locale generation and force usage

The new section accurately reflects the idempotent rake task behavior, force flag semantics, and Shakapacker precompile_hook integration; the examples are clear and executable as written.

spec/react_on_rails/locales_spec.rb (1)

40-54: Force flag propagation tests look solid

The new specs neatly assert that .compile(force: true) forwards force: true to both ToJson and ToJs depending on i18n_output_format, giving good coverage of the new API surface without over‑specifying the implementation.

lib/tasks/locale.rake (1)

13-20: Rake task force handling is clear and matches the API

The task’s help text and force parsing are consistent with the new Locales.compile(force:) signature, and the case‑insensitive handling of "true", "1", and "yes" provides a nice bit of flexibility beyond the documented force=true usage.

@claude
Copy link

claude bot commented Nov 22, 2025

Code Review: Idempotent Locale Generation

I've reviewed this PR and overall it's a well-implemented, high-quality feature that addresses a real pain point. The implementation is clean, well-tested, and follows the project's conventions. Here's my detailed feedback:


✅ Strengths

1. Solid Implementation Design

  • The idempotent logic is correctly placed in Base.initialize where it can handle both ToJs and ToJson subclasses
  • Early return pattern keeps the code clean and readable
  • Force flag propagates cleanly through the call chain: rake task → Locales.compile → Base.new

2. Robust Timestamp Logic

The files_are_outdated method is well thought out:

def files_are_outdated
  return true if locale_files.empty?  # Edge case handled!
  
  latest_yml = locale_files.map { |file| File.mtime(file) }.max
  earliest = exist_files.map { |file| File.mtime(file) }.min
  latest_yml > earliest
end
  • Correctly compares latest source vs earliest output (conservative approach)
  • Edge case for empty locale_files properly handled (line 67)

3. Excellent Test Coverage

  • Tests verify force parameter propagation through the entire chain
  • Tests confirm force bypasses timestamp checks (lines 54-69 in locales_to_js_spec.rb)
  • Uses realistic file timestamps to verify behavior
  • Good use of shared examples for DRY testing

4. User Experience

  • Clear console feedback ("up to date" vs "generated")
  • Intuitive force flag syntax: force=true, 1, or yes
  • Documentation is comprehensive and includes practical examples

5. Type Safety

The RBS signatures are well-structured and cover all public/private methods in the locale generation system.


🔍 Minor Issues & Suggestions

1. Potential Edge Case: Partial File Generation

Location: lib/react_on_rails/locales/base.rb:62-64

If only one output file exists (e.g., translations.js but not default.js), the method still checks timestamps:

def exist_files
  @exist_files ||= files.select { |file| File.exist?(file) }
end

Scenario: If a user manually deletes default.js but keeps translations.js, the timestamp check might skip regeneration even though a file is missing.

Suggestion: Consider checking that all expected files exist:

def obsolete?
  return true if exist_files.length != files.length  # Missing files
  return true if exist_files.empty?
  
  files_are_outdated
end

Severity: Low - Users can use force=true to recover, but this would make the behavior more robust.


2. Minor: Console Output Goes to STDOUT

Location: lib/react_on_rails/locales/base.rb:43,49

The puts statements go to STDOUT, which is fine for rake tasks but might clutter logs in some environments.

Suggestions:

  • Consider using Rails logger if available: Rails.logger.info (with fallback to puts)
  • Or make it configurable via ReactOnRails.configuration.verbose

Severity: Very Low - Current approach is perfectly acceptable for a rake task.


3. RBS: Missing Return Type for compile

Location: sig/react_on_rails/locales.rbs:7

The signature shows:

def self.compile: (?force: bool) -> (ToJs | ToJson)

Observation: If i18n_dir.nil?, the Base.initialize returns early (line 40), and compile returns the instance but it didn't actually do anything.

Question: Should the return type reflect this? Or is the current signature intentional (returning the instance regardless)?

Severity: Very Low - More of a type accuracy question than a bug.


4. Documentation: Shakapacker Version Requirement

Location: docs/building-features/i18n.md:37

The docs mention "Shakapacker 9.3+" for precompile_hook, but don't explain what happens with older versions.

Suggestion: Add a note like:

**Note:** `precompile_hook` requires Shakapacker 9.3+. For older versions, use manual coordination in Procfile.

Severity: Very Low - Nice to have for completeness.


🎯 Performance Considerations

Excellent performance characteristics:

  • Early return if files up-to-date (no YAML parsing)
  • Memoization of file lists (@locale_files, @exist_files, @files)
  • Minimal file system operations (only mtime checks when skipping)

Benchmark: For a typical app with 10 locale files, skipping should be < 10ms vs 100-500ms for full regeneration.


🔒 Security Considerations

No security concerns identified:

  • Force flag only accepts boolean-like strings (true, 1, yes)
  • No user input directly affects file paths
  • YAML safe loading already handled by existing code

📋 Best Practices Compliance

Follows project conventions:

  • Proper use of frozen_string_literal: true
  • RBS signatures added for new functionality
  • Changelog updated with correct format
  • Tests use shared_examples appropriately
  • Documentation is clear and practical

✅ Final Verdict

Recommendation: APPROVE with optional improvements

This PR is production-ready as-is. The minor suggestions above are enhancements, not blockers. The implementation is:

  • ✅ Well-designed and maintainable
  • ✅ Thoroughly tested
  • ✅ Backward compatible
  • ✅ Properly documented
  • ✅ Performance-conscious

The idempotent behavior solves a real problem (duplicate work in build pipelines) and the force flag provides a good escape hatch.

Optional follow-up: Consider the partial file generation edge case if you want to make the behavior even more robust.


Great work! This is a solid addition to the gem. 🎉

@claude
Copy link

claude bot commented Nov 22, 2025

Code Review - PR #2093: Make locale generation idempotent with force flag support

✅ Overall Assessment

This is a well-designed improvement that adds idempotent behavior to the locale generation task. The implementation is clean, well-tested, and follows project conventions. The force flag provides necessary escape hatch functionality.


🎯 Strengths

1. Clean Implementation

  • The idempotent logic is correctly placed in Base#initialize
  • Early return pattern keeps the code readable
  • User feedback messages are helpful ("skipping generation" vs "generated files")

2. Strong Test Coverage

  • Tests verify force parameter propagation through the compile chain (locales_spec.rb:39-56)
  • Tests verify force regeneration with future timestamps (locales_to_js_spec.rb:52-68)
  • Existing tests already covered the timestamp-based skipping behavior

3. Type Safety

  • Comprehensive RBS signatures added for all Locales classes
  • Covers both public and private methods
  • Good type documentation for future maintainers

4. Documentation

  • Clear usage examples in i18n.md
  • Shakapacker precompile_hook recommendation is excellent
  • Changelog entry is user-focused and explains the benefit

🐛 Potential Issues

1. CRITICAL: Edge case in files_are_outdated (line 64)

Current code:

def files_are_outdated
  return true if locale_files.empty?
  
  latest_yml = locale_files.map { |file| File.mtime(file) }.max
  earliest = exist_files.map { |file| File.mtime(file) }.min
  latest_yml > earliest
end

Issue: If locale_files is empty, this returns true (outdated), but then generate_translations will iterate over an empty array and produce empty translations. This seems incorrect - if there are no source YAML files, we shouldn't generate anything.

Suggested fix:

def files_are_outdated
  return false if locale_files.empty?  # No source files = nothing to generate
  return true if exist_files.empty?    # No output files = need to generate
  
  latest_yml = locale_files.map { |file| File.mtime(file) }.max
  earliest = exist_files.map { |file| File.mtime(file) }.min
  latest_yml > earliest
end

Location: lib/react_on_rails/locales/base.rb:61-67


2. Minor: User message could be more actionable

Current message:

puts "Locale files are up to date, skipping generation. Use force=true to regenerate."

Suggestion: This is good, but could be slightly clearer about where to pass force=true:

puts "Locale files are up to date, skipping generation. Use 'rake react_on_rails:locale force=true' to force regeneration."

This helps users who might think force=true is a Ruby method parameter rather than a rake task argument.

Location: lib/react_on_rails/locales/base.rb:43


🔍 Code Quality Observations

1. Consistent Parameter Flow
The force parameter correctly flows: rake task → Locales.compile → Base.new → ToJs/ToJson.new

2. Backward Compatibility
Default value of force: false ensures existing code continues to work

3. Environment Variable Parsing
The rake task correctly parses truthy values: %w[true 1 yes]

4. RBS Signatures
Type signatures are comprehensive and match the implementation


📊 Performance Considerations

Positive Impact:

  • Eliminates redundant YAML parsing when files are up-to-date
  • Safe for repeated calls (e.g., in Shakapacker's precompile_hook)
  • Minimal overhead: just timestamp comparisons

No concerns identified.


🔒 Security Considerations

No security concerns identified.

The changes don't introduce any security vulnerabilities:

  • No new user input processing beyond the existing ENV["force"]
  • Force flag only bypasses timestamp checks, doesn't change file processing
  • Existing YAML safe loading configuration is unchanged

🧪 Test Coverage Assessment

Coverage: Excellent

What's tested:

  • ✅ Force parameter propagation to ToJson (locales_spec.rb:40-45)
  • ✅ Force parameter propagation to ToJs (locales_spec.rb:47-52)
  • ✅ Force regeneration with future timestamps (locales_to_js_spec.rb:52-68)
  • ✅ Existing timestamp-based skipping (locales_to_js_spec.rb:39-53)

What could be added (optional):

  • Test for empty locale_files behavior (see Issue TODO for first version #1 above)
  • Integration test calling the rake task with force=true

📝 Recommendations

Priority: HIGH

  1. Fix the edge case in files_are_outdated - handle empty locale_files correctly

Priority: LOW
2. Consider improving the user message to include full rake command
3. Consider adding a test for the empty locale_files scenario


🎓 Additional Notes

Follows CLAUDE.md Guidelines:

Changelog Issue: The CHANGELOG.md references "PR 2090" but this is PR #2093. Please verify and update if needed.


✨ Conclusion

This is a quality improvement that solves a real problem (repeated locale generation in Shakapacker workflows). The implementation is solid with one edge case to address. After fixing the files_are_outdated logic, this will be ready to merge.

Recommendation: Approve after addressing the critical edge case.

Great work on the comprehensive testing and documentation! 🚀

justin808 added a commit that referenced this pull request Nov 22, 2025
## Summary

Adds automatic precompile hook coordination to `bin/dev`, eliminating
the need for manual coordination, sleep hacks, and duplicate task calls
in `Procfile.dev`.

**This PR is focused solely on the bin/dev coordination feature.** The
complementary idempotent locale generation feature is in PR #2093.

## How It Works

When you configure a `precompile_hook` in `config/shakapacker.yml`:

```yaml
default: &default
  precompile_hook: 'bundle exec rake react_on_rails:locale'
```

`bin/dev` will now:
1. ✅ Run the hook **once** before starting development processes
2. ✅ Set `SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true` environment variable
3. ✅ Pass the env var to all spawned processes (Rails, webpack, etc.)
4. ✅ Prevent webpack processes from re-running the hook independently

## Before & After

**Before (manual coordination with sleep hacks):**
```procfile
# Procfile.dev
wp-server: sleep 15 && bundle exec rake react_on_rails:locale && bin/shakapacker --watch
```

**After (automatic coordination via bin/dev):**
```procfile
# Procfile.dev
wp-server: bin/shakapacker --watch
```

```yaml
# config/shakapacker.yml
default: &default
  precompile_hook: 'bundle exec rake react_on_rails:locale'
```

Clean, simple, reliable - no sleep hacks required!

## Key Features

**Shakapacker Version Detection**
- Checks if you're using Shakapacker < 9.4.0
- Displays friendly warning about `SHAKAPACKER_SKIP_PRECOMPILE_HOOK`
support
- Recommends upgrading to 9.4.0+ to avoid duplicate hook execution

**Error Handling**
- Exits immediately if precompile hook fails
- Shows clear error message with the failed command
- Suggests fixing or removing the hook from config

**Smart Skipping**
- Skips hook execution for `bin/dev kill` and `bin/dev help` commands
- Only runs hook for actual development modes (hmr, static, prod)

**Help Flag Handling**
- Detects `-h`/`--help` flags early
- Prevents hook execution when user just wants help
- Maintains clean separation between help and runtime logic

## Use Cases

**Locale generation (with PR #2093):**
```yaml
precompile_hook: 'bundle exec rake react_on_rails:locale'
```

**ReScript compilation:**
```yaml
precompile_hook: 'yarn rescript'
```

**Multiple tasks:**
```yaml
precompile_hook: 'bundle exec rake react_on_rails:locale && yarn rescript'
```

Any expensive build task that needs to run before webpack starts!

## Implementation Details

**Modified Files:**
- `lib/react_on_rails/dev/server_manager.rb` - Core coordination logic
- `spec/react_on_rails/dev/server_manager_spec.rb` - Comprehensive test
coverage (161 lines)
- `docs/building-features/process-managers.md` - Feature documentation
- `docs/building-features/i18n.md` - Usage example with locale
generation

**Test Coverage:**
- ✅ Hook execution for all modes (development, static, prod)
- ✅ Environment variable setting across all modes
- ✅ Skipping for kill/help commands
- ✅ Help flag handling (-h/--help)
- ✅ Shakapacker version warning (< 9.4.0)
- ✅ Error handling when hook fails
- ✅ No-hook configuration scenario

## Shakapacker Version Requirements

- **9.3.0+**: `precompile_hook` configuration support
- **9.4.0+**: `SHAKAPACKER_SKIP_PRECOMPILE_HOOK` env var support
(prevents duplicate execution)

If you're on Shakapacker < 9.4.0, `bin/dev` will warn you that the hook
may run multiple times.

## Related PRs

- PR #2093 - Idempotent locale generation (makes `react_on_rails:locale`
safe to call multiple times)
- Issue #2091 - Original feature request

## Breaking Changes

None - this is purely additive functionality.

## Testing

Run the test suite:
```bash
bundle exec rspec spec/react_on_rails/dev/server_manager_spec.rb
```

Manual testing:
```bash
# Configure a precompile_hook in config/shakapacker.yml
echo 'default: &default\n  precompile_hook: "echo Running hook..."' >> config/shakapacker.yml

# Run bin/dev and verify:
bin/dev
# Should see: "🔧 Running Shakapacker precompile hook..."
# Should see: "Running hook..."
# Should see: "✅ Precompile hook completed successfully"
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <[email protected]>
@justin808 justin808 force-pushed the jg-/idempotent-locale-generation branch from dfe3c9e to 43fc2ba Compare November 22, 2025 22:57
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/react_on_rails/locales/base.rb (1)

57-73: Add safeguard to prevent generating empty locale files when no source files exist.

The edge case identified is real. When both exist_files and locale_files are empty, obsolete? returns true (line 58), causing generation to proceed. The generate_translations method then iterates over an empty locale_files collection, returning empty hashes that are written as empty files.

The guard at line 68 only protects the files_are_outdated check when regenerating existing files, not the initial generation decision. Add an explicit check in initialize:

def initialize(force: false)
  return if i18n_dir.nil?
  return if locale_files.empty?
  
  if !force && !obsolete?
    # ... existing skip message
  end
  # ... rest of method
end

This prevents generating empty output files when no source locale files are configured.

🧹 Nitpick comments (1)
lib/react_on_rails/locales/base.rb (1)

42-46: Consider simplifying the condition for readability.

The logic is correct, but the condition !force && !obsolete? could be more readable. Consider:

-if !force && !obsolete?
+if !force && !obsolete?

Actually, the current form is fine. Alternatively, you could make it more explicit:

if obsolete? || force
  # generate
else
  # skip
end

This inverts the logic but may be clearer. However, this is a nitpick—the current implementation is acceptable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2813a9 and dfe3c9e.

📒 Files selected for processing (2)
  • lib/react_on_rails/locales/base.rb (4 hunks)
  • spec/react_on_rails/locales_to_js_spec.rb (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • lib/react_on_rails/locales/base.rb
  • spec/react_on_rails/locales_to_js_spec.rb
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • lib/react_on_rails/locales/base.rb
  • spec/react_on_rails/locales_to_js_spec.rb
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • lib/react_on_rails/locales/base.rb
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • lib/react_on_rails/locales/base.rb
  • spec/react_on_rails/locales_to_js_spec.rb
📚 Learning: 2025-04-09T12:56:10.756Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1696
File: node_package/src/RSCPayloadContainer.ts:0-0
Timestamp: 2025-04-09T12:56:10.756Z
Learning: In the react_on_rails codebase, RSC payloads are already stringified using `JSON.stringify()` before being processed by the `escapeScript` function, which handles escaping of special characters. The function only needs to handle specific HTML markers like comments and closing script tags.

Applied to files:

  • lib/react_on_rails/locales/base.rb
🧬 Code graph analysis (1)
lib/react_on_rails/locales/base.rb (1)
lib/react_on_rails/configuration.rb (1)
  • initialize (134-190)
🔇 Additional comments (3)
spec/react_on_rails/locales_to_js_spec.rb (1)

54-72: LGTM! Test correctly validates force regeneration.

The test properly verifies that force: true bypasses the up-to-date check and regenerates files. The use of sleep 0.01 helps ensure timestamp differentiation on fast filesystems, and the assertions correctly validate that:

  1. The file mtime changes from the artificially set future time
  2. The new mtime is greater than the initial generation time
lib/react_on_rails/locales/base.rb (2)

7-21: LGTM! Backward-compatible force parameter addition.

The force parameter is properly added with a default value of false, ensuring backward compatibility. The parameter is correctly propagated to both ToJs and ToJson constructors, which inherit from Base and accept the force parameter.


50-50: Good addition of user feedback.

The message clearly communicates successful generation to users, improving the developer experience when running the rake task.

@justin808
Copy link
Member Author

@claude review this PR

@claude
Copy link

claude bot commented Nov 22, 2025

Claude finished @justin808's task —— View job


Code Review for PR #2093

I've completed a thorough review of this PR. Overall, this is excellent work that implements idempotent locale generation cleanly and safely. The implementation is well-designed, thoroughly tested, and properly documented.

Review Tasks

  • Read and understand all changed files
  • Review implementation correctness and edge cases
  • Verify test coverage
  • Check RBS type signatures
  • Review documentation updates
  • Synthesize findings and provide feedback

Summary

Recommendation: APPROVE

This PR successfully adds idempotent behavior to the react_on_rails:locale rake task with a force flag override. The implementation is solid, backward-compatible, and follows all project conventions. I found no blocking issues, only minor suggestions for potential improvements.


Strengths

1. Clean Idempotent Design

The timestamp-based approach is efficient and correct:

  • Compares latest YAML source mtime against earliest output file mtime (conservative approach)
  • Early return prevents unnecessary file operations when up-to-date
  • Force flag provides necessary escape hatch

Code reference: lib/react_on_rails/locales/base.rb:42-46, 67-73

2. Proper Parameter Flow

The force parameter flows cleanly through the entire chain:

rake task → Locales.compile(force:) → Base.new(force:) → ToJs/ToJson.new(force:)

Tests verify this at each level (lines 40-54 in spec/react_on_rails/locales_spec.rb).

3. Comprehensive Test Coverage

  • Force parameter propagation tested for both ToJs and ToJson paths
  • Force flag bypass verified with future timestamps (lines 54-72 in spec/react_on_rails/locales_to_js_spec.rb)
  • Existing shared examples ensure idempotent behavior works correctly
  • Good use of sleep 0.01 to ensure timestamp differentiation on fast filesystems

4. Type Safety with RBS

Complete type signatures added in sig/react_on_rails/locales.rbs:

  • All public and private methods covered
  • Correct return types (ToJs | ToJson union for compile)
  • Optional force parameter properly typed

5. User-Friendly Documentation

The docs/building-features/i18n.md update is excellent:

  • Clear explanation of idempotent behavior and benefits
  • Practical examples for both normal and forced runs
  • Excellent: Recommended pattern using Shakapacker's precompile_hook (the primary use case)
  • Alternative manual coordination approach documented
  • Good note about linting issues when translations aren't built

6. Proper Changelog Entry

CHANGELOG.md entry is well-formatted and accurately describes the feature (line 36).


Edge Cases & Observations

1. Empty locale_files Handling - Intentional Design ✓

Location: lib/react_on_rails/locales/base.rb:68

The code includes this guard:

def files_are_outdated
  return false if locale_files.empty? # No source files = nothing to generate
  # ...
end

This was added in commit 43fc2ba to address an edge case. The behavior is:

  • If no YAML files exist, files_are_outdated returns false
  • Combined with obsolete? logic (line 57-60), this means:
    • If output files don't exist and no YAML files exist → still tries to generate (empty files)
    • If output files exist and no YAML files exist → skips generation

Analysis: The current behavior seems correct for most cases. If someone misconfigures i18n_yml_dir to point to an empty directory, they'll get empty translation files once, then subsequent runs will skip. This is safe and discoverable.

Potential improvement (optional): You could add a warning when locale_files.empty? and no output files exist, to help users catch configuration errors. But this is not necessary for the feature to work correctly.

2. Partial File Generation - Protected by Design ✓

Location: lib/react_on_rails/locales/base.rb:62-64, 70-72

I verified that partial file deletion is handled safely:

def obsolete?
  return true if exist_files.empty?  # Any missing file triggers regeneration
  files_are_outdated
end

If a user manually deletes one output file (e.g., default.json), exist_files will have fewer elements than files, but the timestamp comparison still uses exist_files.min, so:

  • If the remaining file is newer than source YAML → skips (incorrect behavior)
  • If the remaining file is older than source YAML → regenerates (correct)

However, the obsolete? method only returns true if all output files are missing. If only one file is missing, it relies on timestamp comparison.

Suggestion (optional): Consider adding a check to ensure all expected files exist:

def obsolete?
  return true if exist_files.length != files.length  # Missing some files
  return true if exist_files.empty?
  files_are_outdated
end

This would make the behavior more robust if users accidentally delete individual files. However, this is an edge case that can be solved with force=true, so it's not blocking.

3. User Feedback Messages - Good UX ✓

Location: lib/react_on_rails/locales/base.rb:43-44, 50

The console messages are clear and helpful:

  • "Locale files are up to date, skipping generation. Use 'rake react_on_rails:locale force=true' to force regeneration."
  • "Generated locale files in {i18n_dir}"

These will appear in CI logs and development output, which is appropriate for a rake task. Good UX choice.


Implementation Quality

Code Quality: Excellent

  • Clean separation of concerns (Base class, subclasses)
  • Proper use of memoization (@exist_files, @locale_files)
  • Conservative timestamp comparison (latest source vs earliest output)
  • Backward compatible (force defaults to false)

Error Handling: Good

  • Proper handling of nil i18n_dir (early return, line 40)
  • YAML parsing errors are caught with helpful messages (lines 132-136 in base.rb)
  • Unsafe YAML handling documented and configurable

Performance: Excellent

  • Minimal file system operations when skipping (only mtime checks)
  • No YAML parsing when files are up-to-date
  • Benchmark: For 10 locale files, skipping should be <10ms vs 100-500ms for full generation

RBS Type Signature Review

Location: sig/react_on_rails/locales.rbs

The signatures are accurate and comprehensive:

Correct types:

  • compile: (?force: bool) -> (ToJs | ToJson) - union type is correct
  • initialize: (?force: bool) -> void - optional parameter properly typed
  • All private methods have appropriate signatures

Minor observation (not an issue):

  • Line 18: locale_files: () -> Array[String]
    • The implementation shows it can return nil in some cases (line 88-94 uses || operator), but checking the code more carefully, it returns an array or nil, and the nil is only used internally. The caller always gets an array or doesn't get anything (early return at line 40).
    • Verdict: Current signature is fine because nil triggers early return before locale_files is used.

Documentation Review

CHANGELOG.md

Line 36: Entry is properly formatted and references the correct PR (2090, not 2092 or 2093).

Note: I see some confusion in the review comments about PR numbers. Looking at the actual code:

Clarification needed: Is this PR #2093 or #2090? The changelog should reference the actual merged PR number. If this PR will be merged as #2093, update the changelog. If #2090 was the original implementation and this is a follow-up, the current reference is correct.

i18n.md Documentation

Lines 24-58: The documentation update is excellent:

  • Clear explanation of idempotent behavior
  • Practical examples
  • Recommended approach using Shakapacker's precompile_hook (perfect for the use case)
  • Alternative manual approach documented
  • Good cross-reference to process-managers.md

Testing Assessment

Test Coverage: Excellent

Unit Tests:

  • ✅ Force parameter propagation (both ToJs and ToJson)
  • ✅ Force flag bypasses timestamp check
  • ✅ Idempotent behavior (shared examples)
  • ✅ YAML error handling

Integration Tests:

  • ✅ Up-to-date file skipping (lines 39-52 in locales_to_js_spec.rb)
  • ✅ Obsolete file regeneration (lines 13-36)
  • ✅ Force regeneration with future timestamps (lines 54-72)

Coverage gaps (not blocking):

  • Empty locale_files scenario (covered by the guard at line 68, but not explicitly tested)
  • Partial file deletion scenario (user can use force=true to recover)

Security & Performance

Security: No Concerns

  • Force flag is boolean, no injection risk
  • ENV parsing uses whitelist (%w[true 1 yes])
  • File operations use existing configuration paths
  • No user input directly affects file paths

Performance: Excellent

  • Timestamp checking is O(n) where n = number of files (very fast)
  • Skips expensive YAML parsing and file generation when up-to-date
  • Safe to call multiple times in precompile hooks
  • No performance regressions

Compliance with Project Guidelines

CLAUDE.md Compliance:

  • ✅ RBS signatures added as required
  • ✅ Tests follow existing RSpec patterns
  • ✅ Documentation updated appropriately
  • ✅ Changelog entry follows format guidelines
  • ✅ Code uses frozen_string_literal: true
  • ✅ Backward compatible (no breaking changes)

Minor Suggestions (Optional)

These are nice-to-haves, not blockers:

  1. Consider adding partial file check (as mentioned in edge case Make work with turbolinks and better helper #2):

    def obsolete?
      return true if exist_files.length != files.length
      return true if exist_files.empty?
      files_are_outdated
    end
  2. Consider adding warning for empty locale_files (when debugging misconfiguration):

    def initialize(force: false)
      return if i18n_dir.nil?
      
      if locale_files.empty?
        puts "Warning: No locale files found in #{i18n_yml_dir || 'Rails i18n load path'}"
        return
      end
      # ...
    end
  3. Clarify PR number in CHANGELOG - Verify whether this should reference Coordinate assets:precompile with Shakapacker Precompile Hooks #2090 or Make locale generation idempotent with force flag support #2093.


Final Verdict

APPROVE - Ready to Merge

This is high-quality work that:

  • Solves a real problem (duplicate work in build pipelines)
  • Is well-designed and maintainable
  • Has comprehensive test coverage
  • Follows all project conventions
  • Is backward compatible
  • Includes excellent documentation

The implementation is production-ready as-is. The suggestions above are optional enhancements that could be addressed in follow-up work if desired.

Great job on this PR! 🎉


justin808 and others added 5 commits November 22, 2025 13:05
The `react_on_rails:locale` rake task is now idempotent, automatically
skipping generation when locale files are already up-to-date. This
addresses issue #2090 by making it safe to call multiple times (e.g.,
in Shakapacker's `precompile_hook`) without duplicate work or race
conditions.

- **Idempotent behavior**: Task skips regeneration when locale files
  are newer than source YAML files
- **Force flag**: Added `force=true` option to override timestamp
  checking and force regeneration
- **Clear messaging**: Outputs helpful messages when skipping or
  generating files
- **Safe coordination**: Can be called multiple times in precompile
  hooks, development servers, and CI without issues

- `lib/tasks/locale.rake`: Added `force=true` parameter support and
  improved task description
- `lib/react_on_rails/locales/base.rb`: Modified `compile()` and
  `Base#initialize()` to accept and handle `force` parameter, added
  informative output messages

- `spec/react_on_rails/locales_spec.rb`: Added tests for force
  parameter propagation to ToJson and ToJs
- `spec/react_on_rails/locales_to_js_spec.rb`: Added test verifying
  force flag bypasses timestamp checking

- `docs/building-features/i18n.md`: Added recommended pattern for using
  Shakapacker's `precompile_hook` with idempotent locale generation
- `CHANGELOG.md`: Added entry documenting the improvement

```bash
bundle exec rake react_on_rails:locale
```

```bash
bundle exec rake react_on_rails:locale force=true
```

```yaml
default: &default
  precompile_hook: "bundle exec rake react_on_rails:locale"
```

Fixes #2090

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Changes:
- Fix CHANGELOG PR reference (2091 → 2092)
- Add safety guard for empty locale_files array to prevent nil comparison errors
- Improve ENV variable parsing to accept force=true|1|yes (more flexible)
- Remove unnecessary sleep in test (timestamp difference already guaranteed)
- Add RBS type signatures for Locales module and classes

All tests pass and RuboCop clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Remove references to bin/dev automatic precompile hook coordination,
as that feature will be in a separate PR.
1. Fixed critical edge case in files_are_outdated:
   - Changed to return false when locale_files is empty
   - Prevents generation of empty translation files when no source YAML exists
   - Previously returned true (outdated), which would trigger empty generation

2. Improved user message clarity:
   - Updated message to show exact rake task syntax
   - Changed from "Use force=true" to "Use 'rake react_on_rails:locale force=true'"
   - Helps users understand how to pass the parameter correctly

3. Fixed timing-dependent test flake:
   - Added 10ms sleep in spec to ensure different timestamps on fast filesystems
   - Prevents CI failures where file regeneration happens within same timestamp
   - Test was failing in CI due to mtime equality instead of greater-than

All tests pass and RuboCop clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Fixed formatting issues from merge conflict resolution.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@justin808 justin808 force-pushed the jg-/idempotent-locale-generation branch from fa23781 to 22e9d96 Compare November 22, 2025 23:05
@justin808
Copy link
Member Author

@claude review this PR

@claude
Copy link

claude bot commented Nov 23, 2025

Claude finished @justin808's task —— View job


Code Review for PR #2093

I've completed a thorough review of this PR. Overall, this is excellent work that implements idempotent locale generation cleanly and safely. The implementation is well-designed, thoroughly tested, and follows project conventions. The force flag provides necessary escape hatch functionality.

Review Tasks

  • Read and understand all changed files
  • Review implementation correctness and edge cases
  • Verify test coverage
  • Check RBS type signatures
  • Review documentation updates
  • Synthesize findings and provide feedback

Summary

Recommendation: APPROVE

This PR successfully adds idempotent behavior to the react_on_rails:locale rake task with a force flag override. The implementation is solid, backward-compatible, and follows all project conventions. I found no blocking issues, only minor suggestions for potential improvements.


Strengths

1. Clean Idempotent Design

The timestamp-based approach is efficient and correct:

  • Compares latest YAML source mtime against earliest output file mtime (conservative approach)
  • Early return prevents unnecessary file operations when up-to-date
  • Force flag provides necessary escape hatch

Code reference: lib/react_on_rails/locales/base.rb:42-46, 67-73

2. Proper Parameter Flow

The force parameter flows cleanly through the entire chain:

rake task → Locales.compile(force:) → Base.new(force:) → ToJs/ToJson.new(force:)

Tests verify this at each level (lines 40-54 in spec/react_on_rails/locales_spec.rb).

3. Comprehensive Test Coverage

  • Force parameter propagation tested for both ToJs and ToJson paths
  • Force flag bypass verified with future timestamps (lines 54-72 in spec/react_on_rails/locales_to_js_spec.rb)
  • Existing shared examples ensure idempotent behavior works correctly
  • Good use of sleep 0.01 to ensure timestamp differentiation on fast filesystems

4. Type Safety with RBS

Complete type signatures added in sig/react_on_rails/locales.rbs:

  • All public and private methods covered
  • Correct return types (ToJs | ToJson union for compile)
  • Optional force parameter properly typed

5. User-Friendly Documentation

The docs/building-features/i18n.md update is excellent:

  • Clear explanation of idempotent behavior and benefits
  • Practical examples for both normal and forced runs
  • Excellent: Recommended pattern using Shakapacker's precompile_hook (the primary use case)
  • Alternative manual coordination approach documented
  • Good note about linting issues when translations aren't built

6. Proper Changelog Entry

CHANGELOG.md entry is well-formatted and accurately describes the feature (line 36).


Edge Cases & Observations

1. Empty locale_files Handling - Intentional Design ✓

Location: lib/react_on_rails/locales/base.rb:68

The code includes this guard:

def files_are_outdated
  return false if locale_files.empty? # No source files = nothing to generate
  # ...
end

This was added in commit 43fc2ba to address an edge case. The behavior is:

  • If no YAML files exist, files_are_outdated returns false
  • Combined with obsolete? logic (line 57-60), this means:
    • If output files don't exist and no YAML files exist → still tries to generate (empty files)
    • If output files exist and no YAML files exist → skips generation

Analysis: The current behavior seems correct for most cases. If someone misconfigures i18n_yml_dir to point to an empty directory, they'll get empty translation files once, then subsequent runs will skip. This is safe and discoverable.

Potential improvement (optional): You could add a warning when locale_files.empty? and no output files exist, to help users catch configuration errors. But this is not necessary for the feature to work correctly.

2. Partial File Generation - Protected by Design ✓

Location: lib/react_on_rails/locales/base.rb:62-64, 70-72

I verified that partial file deletion is handled safely:

def obsolete?
  return true if exist_files.empty?  # Any missing file triggers regeneration
  files_are_outdated
end

If a user manually deletes one output file (e.g., default.json), exist_files will have fewer elements than files, but the timestamp comparison still uses exist_files.min, so:

  • If the remaining file is newer than source YAML → skips (incorrect behavior)
  • If the remaining file is older than source YAML → regenerates (correct)

However, the obsolete? method only returns true if all output files are missing. If only one file is missing, it relies on timestamp comparison.

Suggestion (optional): Consider adding a check to ensure all expected files exist:

def obsolete?
  return true if exist_files.length != files.length  # Missing some files
  return true if exist_files.empty?
  files_are_outdated
end

This would make the behavior more robust if users accidentally delete individual files. However, this is an edge case that can be solved with force=true, so it's not blocking.

3. User Feedback Messages - Good UX ✓

Location: lib/react_on_rails/locales/base.rb:43-44, 50

The console messages are clear and helpful:

  • "Locale files are up to date, skipping generation. Use 'rake react_on_rails:locale force=true' to force regeneration."
  • "Generated locale files in {i18n_dir}"

These will appear in CI logs and development output, which is appropriate for a rake task. Good UX choice.


Implementation Quality

Code Quality: Excellent

  • Clean separation of concerns (Base class, subclasses)
  • Proper use of memoization (@exist_files, @locale_files)
  • Conservative timestamp comparison (latest source vs earliest output)
  • Backward compatible (force defaults to false)

Error Handling: Good

  • Proper handling of nil i18n_dir (early return, line 40)
  • YAML parsing errors are caught with helpful messages (lines 132-136 in base.rb)
  • Unsafe YAML handling documented and configurable

Performance: Excellent

  • Minimal file system operations when skipping (only mtime checks)
  • No YAML parsing when files are up-to-date
  • Benchmark: For 10 locale files, skipping should be <10ms vs 100-500ms for full generation

RBS Type Signature Review

Location: sig/react_on_rails/locales.rbs

The signatures are accurate and comprehensive:

Correct types:

  • compile: (?force: bool) -> (ToJs | ToJson) - union type is correct
  • initialize: (?force: bool) -> void - optional parameter properly typed
  • All private methods have appropriate signatures

Minor observation (not an issue):

  • Line 18: locale_files: () -> Array[String]
    • The implementation shows it can return nil in some cases (line 88-94 uses || operator), but checking the code more carefully, it returns an array or nil, and the nil is only used internally. The caller always gets an array or doesn't get anything (early return at line 40).
    • Verdict: Current signature is fine because nil triggers early return before locale_files is used.

Documentation Review

CHANGELOG.md

Line 36: Entry is properly formatted and references the correct PR (2090, not 2092 or 2093).

Note: I see some confusion in the review comments about PR numbers. Looking at the actual code:

Clarification needed: Is this PR #2093 or #2090? The changelog should reference the actual merged PR number. If this PR will be merged as #2093, update the changelog. If #2090 was the original implementation and this is a follow-up, the current reference is correct.

i18n.md Documentation

Lines 24-58: The documentation update is excellent:

  • Clear explanation of idempotent behavior
  • Practical examples
  • Recommended approach using Shakapacker's precompile_hook (perfect for the use case)
  • Alternative manual approach documented
  • Good cross-reference to process-managers.md

Testing Assessment

Test Coverage: Excellent

Unit Tests:

  • ✅ Force parameter propagation (both ToJs and ToJson)
  • ✅ Force flag bypasses timestamp check
  • ✅ Idempotent behavior (shared examples)
  • ✅ YAML error handling

Integration Tests:

  • ✅ Up-to-date file skipping (lines 39-52 in locales_to_js_spec.rb)
  • ✅ Obsolete file regeneration (lines 13-36)
  • ✅ Force regeneration with future timestamps (lines 54-72)

Coverage gaps (not blocking):

  • Empty locale_files scenario (covered by the guard at line 68, but not explicitly tested)
  • Partial file deletion scenario (user can use force=true to recover)

Security & Performance

Security: No Concerns

  • Force flag is boolean, no injection risk
  • ENV parsing uses whitelist (%w[true 1 yes])
  • File operations use existing configuration paths
  • No user input directly affects file paths

Performance: Excellent

  • Timestamp checking is O(n) where n = number of files (very fast)
  • Skips expensive YAML parsing and file generation when up-to-date
  • Safe to call multiple times in precompile hooks
  • No performance regressions

Compliance with Project Guidelines

CLAUDE.md Compliance:

  • ✅ RBS signatures added as required
  • ✅ Tests follow existing RSpec patterns
  • ✅ Documentation updated appropriately
  • ✅ Changelog entry follows format guidelines
  • ✅ Code uses frozen_string_literal: true
  • ✅ Backward compatible (no breaking changes)

Minor Suggestions (Optional)

These are nice-to-haves, not blockers:

  1. Consider adding partial file check (as mentioned in edge case Make work with turbolinks and better helper #2):

    def obsolete?
      return true if exist_files.length != files.length
      return true if exist_files.empty?
      files_are_outdated
    end
  2. Consider adding warning for empty locale_files (when debugging misconfiguration):

    def initialize(force: false)
      return if i18n_dir.nil?
      
      if locale_files.empty?
        puts "Warning: No locale files found in #{i18n_yml_dir || 'Rails i18n load path'}"
        return
      end
      # ...
    end
  3. Clarify PR number in CHANGELOG - Verify whether this should reference Coordinate assets:precompile with Shakapacker Precompile Hooks #2090 or Make locale generation idempotent with force flag support #2093.


Final Verdict

APPROVE - Ready to Merge

This is high-quality work that:

  • Solves a real problem (duplicate work in build pipelines)
  • Is well-designed and maintainable
  • Has comprehensive test coverage
  • Follows all project conventions
  • Is backward compatible
  • Includes excellent documentation

The implementation is production-ready as-is. The suggestions above are optional enhancements that could be addressed in follow-up work if desired.

Great job on this PR! 🎉


View job

1. Add partial file check in obsolete? method:
   - Detect when only some output files exist (incomplete generation)
   - Triggers regeneration to ensure all files are present

2. Add warning for empty locale files:
   - Shows clear message when no source YAML files are found
   - Helps users debug configuration issues
   - Exits early to avoid confusing error messages

3. Simplify files_are_outdated logic:
   - Removed redundant empty check (now handled in initialize)
   - Cleaner separation of concerns

All tests pass with these improvements.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
spec/react_on_rails/locales_to_js_spec.rb (1)

54-72: LGTM! Well-structured test for force regeneration.

The test correctly validates that force: true bypasses the timestamp check and regenerates files even when they're already up-to-date. The assertions properly verify both that the mtime changed from the touched future_time and that it progressed beyond the initial generation time.

Minor note: The sleep 0.01 on line 59 ensures timestamp differentiation on fast filesystems. While this is a common pattern in timestamp-based tests, it could theoretically be fragile on extremely slow systems. However, it's acceptable for test purposes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfe3c9e and e31ed95.

📒 Files selected for processing (7)
  • CHANGELOG.md (1 hunks)
  • docs/building-features/i18n.md (1 hunks)
  • lib/react_on_rails/locales/base.rb (3 hunks)
  • lib/tasks/locale.rake (1 hunks)
  • sig/react_on_rails/locales.rbs (1 hunks)
  • spec/react_on_rails/locales_spec.rb (1 hunks)
  • spec/react_on_rails/locales_to_js_spec.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • lib/react_on_rails/locales/base.rb
  • lib/tasks/locale.rake
  • CHANGELOG.md
  • sig/react_on_rails/locales.rbs
  • docs/building-features/i18n.md
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • spec/react_on_rails/locales_spec.rb
  • spec/react_on_rails/locales_to_js_spec.rb
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • spec/react_on_rails/locales_spec.rb
  • spec/react_on_rails/locales_to_js_spec.rb
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • spec/react_on_rails/locales_to_js_spec.rb
🧬 Code graph analysis (1)
spec/react_on_rails/locales_spec.rb (2)
lib/react_on_rails/configuration.rb (1)
  • configuration (33-71)
lib/react_on_rails/locales/base.rb (1)
  • compile (7-22)
🔇 Additional comments (1)
spec/react_on_rails/locales_spec.rb (1)

40-54: LGTM! Comprehensive coverage of force parameter propagation.

Both tests correctly verify that compile(force: true) properly passes the force parameter to the respective concrete classes (ToJson and ToJs) based on the configured output format. The use of mock expectations is appropriate for unit testing parameter propagation at this level.

@justin808 justin808 merged commit 3f21898 into master Nov 23, 2025
31 checks passed
@justin808 justin808 deleted the jg-/idempotent-locale-generation branch November 23, 2025 00:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants