Class: LcpRuby::I18nCheck::KeyDeriver

Inherits:
Object
  • Object
show all
Defined in:
lib/lcp_ruby/i18n_check/key_deriver.rb

Overview

Computes the canonical i18n key (or ordered lookup chain) for every labeled metadata element the lint inspects. Mirrors the runtime ‘I18n.t(…)` call sites — see `docs/design/i18n_consistency_check.md` §“Tier 1 vs Tier 2” for the canonical-key table.

Stateless. Used by ‘RegistryWalker` to compute keys for the missing-translation check and for suggested-fix messaging.

Defined Under Namespace

Classes: UnknownKindError

Constant Summary collapse

TIER1_TEMPLATES =

Tier 1 — runtime calls ‘I18n.t(<key>, default: …)` on these. Lint checks each key against every configured locale; both `:missing_translation` and `:literal_in_dsl` may fire. Array values mean “ordered lookup chain”: the lint considers a key resolved if ANY entry resolves; missing-translation fires only when all entries are absent in a given locale.

{
  presenter_label:        "lcp_ruby.models.%{model}.other",
  presenter_description:  "lcp_ruby.presenters.%{name}.%{view}.description",
  presenter_section:      "lcp_ruby.presenters.%{name}.sections.%{section_key}",
  presenter_form_submit:  "lcp_ruby.presenters.%{name}.form.submitting.%{action}",
  model_label_singular:   "lcp_ruby.models.%{name}.one",
  model_label_plural:     "lcp_ruby.models.%{name}.other",
  model_field:            [
    "lcp_ruby.models.%{model}.fields.%{field}",
    "lcp_ruby.fields.%{field}"
  ],
  model_enum_value:       [
    "lcp_ruby.models.%{model}.enums.%{field}.%{value}",
    "lcp_ruby.enums.%{model}.%{field}.%{value}"
  ],
  menu_entry:             "lcp_ruby.menu.%{label_slug}",
  menu_aria_label:        "lcp_ruby.menu.aria.%{label_slug}",
  workflow_state:         "lcp_ruby.workflows.%{workflow}.states.%{state}",
  workflow_transition:    "lcp_ruby.workflows.%{workflow}.transitions.%{transition}",
  custom_action_label:    "lcp_ruby.actions.form.%{action}",
  scope_label:            "lcp_ruby.scopes.%{name}",
  parameter_label:        "lcp_ruby.parameters.%{name}"
}.freeze
TIER2_KINDS =

Tier 2 — runtime renders the literal directly. ‘derive` returns nil so callers know to skip missing-translation checks.

%i[
  presenter_column_label
  presenter_field_label_override
  presenter_filter_label
  presenter_saved_filter_label
  presenter_tab_label
  presenter_step_label
  view_group_view_label
  workflow_state_description
  type_label
  nested_fields_add_label
].to_set.freeze

Instance Method Summary collapse

Instance Method Details

#derive(kind:, **args) ⇒ Object

Returns an Array of one or more keys (the lookup chain), or nil for Tier 2 kinds. Raises ‘UnknownKindError` for unrecognised kinds —the walker should always hand-pick from the documented list.



63
64
65
66
67
68
69
70
71
# File 'lib/lcp_ruby/i18n_check/key_deriver.rb', line 63

def derive(kind:, **args)
  return nil if TIER2_KINDS.include?(kind)

  template = TIER1_TEMPLATES.fetch(kind) do
    raise UnknownKindError, "unknown kind #{kind.inspect}; " \
                            "expected one of #{TIER1_TEMPLATES.keys + TIER2_KINDS.to_a}"
  end
  Array(template).map { |t| format(t, args.transform_values(&:to_s)) }
end

#expected_humanize_for(kind, args) ⇒ Object

The runtime “default” fallback that would render when the locale entry is missing. The walker compares the actual label against this to decide whether the author “overrode the default” (and therefore introduced a literal worth flagging).

Returns nil for kinds whose runtime fallback IS the literal itself (menu_entry, workflow_transition, workflow_state) — those use missing-only walker semantics, so the comparison never runs.



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/lcp_ruby/i18n_check/key_deriver.rb', line 99

def expected_humanize_for(kind, args)
  case kind
  when :presenter_label,
       :model_label_singular,
       :scope_label,
       :parameter_label
    args[:name].to_s.humanize
  when :model_label_plural
    args[:name].to_s.humanize.pluralize
  when :presenter_section
    args[:section_key].to_s.humanize
  when :presenter_form_submit, :custom_action_label
    args[:action].to_s.humanize
  when :model_field
    args[:field].to_s.humanize
  when :model_enum_value
    args[:value].to_s.humanize
  when :presenter_description
    # Presenter descriptions have no humanize fallback — empty string
    # treated as "any literal is an override".
    ""
  when :menu_entry, :menu_aria_label, :workflow_state, :workflow_transition
    # Walker uses missing_only: true for these kinds; the comparison
    # is never invoked. nil is the explicit signal.
    nil
  end
end

#identifier_like?(value) ⇒ Boolean

Single source of truth for “looks like a code identifier rather than UI text.” Delegates to the ‘Heuristics` module so the walker’s Tier 2 path can suppress noise from snake_case enum keys, short ALL-CAPS acronyms, etc.

Returns:

  • (Boolean)


131
132
133
# File 'lib/lcp_ruby/i18n_check/key_deriver.rb', line 131

def identifier_like?(value)
  Heuristics.identifier_like?(value)
end

#parameterize_menu_label(label) ⇒ Object

Menu entries key off the parameterized rendered label, NOT the YAML path or any explicit identifier (see ‘lib/lcp_ruby/metadata/menu_item.rb`).



87
88
89
# File 'lib/lcp_ruby/i18n_check/key_deriver.rb', line 87

def parameterize_menu_label(label)
  label.to_s.parameterize(separator: "_")
end

#parameterize_section(raw_title) ⇒ Object

Section keys are runtime-parameterized to snake_case before lookup (see ‘app/helpers/lcp_ruby/layout_helper.rb`). Walker must apply the same transformation when deriving the key from the raw title.



81
82
83
# File 'lib/lcp_ruby/i18n_check/key_deriver.rb', line 81

def parameterize_section(raw_title)
  raw_title.to_s.parameterize(separator: "_")
end

#tier2?(kind) ⇒ Boolean

Returns true iff ‘kind` is recognised as Tier 2.

Returns:

  • (Boolean)


74
75
76
# File 'lib/lcp_ruby/i18n_check/key_deriver.rb', line 74

def tier2?(kind)
  TIER2_KINDS.include?(kind)
end