Module: Howdoc::Narrator

Defined in:
lib/howdoc/narrator.rb

Overview

Turns an action and its payload into a sentence. The engine never contains a sentence itself -- every one of them is an I18n key, so the vocabulary is translatable and an application can reword any instruction without touching the code that records it.

Nothing here is settled while a test runs. A guide is recorded once and written out in every language it is published in, so a sentence is composed at writing time, once per language.

Defined Under Namespace

Classes: Field

Constant Summary collapse

ROOT =
'howdoc'
ACTIONS =
:actions
FIELDS =
:fields
DOCUMENTS =
:documents

Class Method Summary collapse

Class Method Details

.call(action, locale:, **payload) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
# File 'lib/howdoc/narrator.rb', line 48

def call(action, locale:, **payload)
  sentence = lookup(ACTIONS, action, locale:)

  if sentence.nil?
    record_missing(action_key(action), locale)
    return nil
  end

  values = resolve(payload, locale)
  values.empty? ? sentence : I18n.interpolate(sentence, values)
end

.document_text(field, key:, locale:, default: nil) ⇒ Object

A guide's own words -- its title, its introduction -- come from the test that recorded it and are therefore written in one language. Translating howdoc.documents..title gives the same guide a heading in another language without the test knowing there is another language.



92
93
94
95
96
# File 'lib/howdoc/narrator.rb', line 92

def document_text(field, key:, locale:, default: nil)
  return default if key.nil? || key.empty?

  lookup(DOCUMENTS, key, field, locale:) || default
end

.field(locator) ⇒ Object

Wraps a form field locator so it is named in the language of whichever edition is being written, rather than in the one the test ran in.



44
45
46
# File 'lib/howdoc/narrator.rb', line 44

def field(locator)
  Field.new(locator)
end

.field_label(locator, locale:) ⇒ Object

The label a reader would recognise for a form field.

A translation under howdoc.fields wins, which is how a field whose generated name reads badly gets a proper name without any code knowing about it. Otherwise the configured convention applies.



82
83
84
85
86
# File 'lib/howdoc/narrator.rb', line 82

def field_label(locator, locale:)
  return '' if locator.nil?

  lookup(FIELDS, locator, locale:) || Howdoc.config.field_label.call(locator)
end

.humanize_locator(locator) ⇒ Object

Rails names fields after the model that owns them, which is noise to anyone who does not know the schema, so the leading segment is dropped.



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/howdoc/narrator.rb', line 108

def humanize_locator(locator)
  string = locator.to_s

  if string.include?('[')
    string.split('[').last.tr(']', '').tr('_', ' ').strip
  elsif string.include?('_')
    string.split('_').drop(1).join(' ')
  else
    string
  end
end

.known?(action, locales) ⇒ Boolean

Whether any of the languages a guide is published in has a sentence for this action. A step no language can say anything about is not recorded at all -- it would otherwise leave a numbered instruction with nothing in it, or an illustration of nothing.

Returns:

  • (Boolean)


64
65
66
67
68
69
70
71
# File 'lib/howdoc/narrator.rb', line 64

def known?(action, locales)
  return false if action.nil?

  return true if locales.any? { |locale| translated?(action, locale) }

  locales.each { |locale| record_missing(action_key(action), locale) }
  false
end

.missing_keysObject

Keys that were asked for but not translated, reported once at the end of a run rather than silently producing a guide with holes in it. A key is missing in a particular language, so the language is part of it.



34
35
36
# File 'lib/howdoc/narrator.rb', line 34

def missing_keys
  @missing_keys ||= []
end

.phrase(*path, locale:, default: nil) ⇒ Object

The gem's own words -- the ones its templates print rather than the ones it narrates a step with. Looked up the safe way, so an application whose I18n cascades cannot answer an untranslated "howdoc.index.intro" with some unrelated top-level "intro" of its own.



102
103
104
# File 'lib/howdoc/narrator.rb', line 102

def phrase(*path, locale:, default: nil)
  lookup(*path, locale:) || default
end

.reset_missing_keysObject



38
39
40
# File 'lib/howdoc/narrator.rb', line 38

def reset_missing_keys
  @missing_keys = []
end

.translated?(action, locale) ⇒ Boolean

Returns:

  • (Boolean)


73
74
75
# File 'lib/howdoc/narrator.rb', line 73

def translated?(action, locale)
  !lookup(ACTIONS, action, locale:).nil?
end