aromancer 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,8 +13,7 @@
13
13
  module Aromancer
14
14
  class Server
15
15
  include Singleton
16
- SERVER_HOST_FILE = ".aromancer_server_host"
17
- SERVER_URL = File.exist?(SERVER_HOST_FILE) ? File.read(SERVER_HOST_FILE).gsub("\n", "") : "http://localhost:3000"
16
+
18
17
  HEADERS = { "Content-Type": "application/json" }
19
18
 
20
19
  class InternalError
@@ -23,26 +22,25 @@ module Aromancer
23
22
  def initialize(status: -1, body: {})
24
23
  @status = status
25
24
  @body = body
25
+ Aromancer::Storage.instance.clear_cache!
26
26
  end
27
27
 
28
28
  def self.build(e)
29
29
  InternalError.new(
30
30
  status: -1,
31
- body: {error_message: e.message[0..78]}.to_json
31
+ body: {error_message: e.inspect.to_s}.to_json
32
32
  )
33
33
  end
34
34
  end
35
35
 
36
- def self.api_key_param
37
- {
38
- api_key: Aromancer::Storage.get_player["api_key"]
39
- }
36
+ def self.auth_header
37
+ {"Authorization": "Bearer #{Aromancer::Storage.get_player["api_key"]}"}
40
38
  end
41
39
 
42
40
  def self.sign_in(email_address, password)
43
41
  begin
44
42
  Faraday.new(
45
- url: SERVER_URL,
43
+ url: Aromancer::Storage.get_server_url,
46
44
  headers: HEADERS,
47
45
  params: {
48
46
  email_address: email_address,
@@ -57,7 +55,7 @@ module Aromancer
57
55
  def self.sign_up(email_address, password, password_confirmation)
58
56
  begin
59
57
  Faraday.new(
60
- url: SERVER_URL,
58
+ url: Aromancer::Storage.get_server_url,
61
59
  headers: HEADERS,
62
60
  params: {
63
61
  user: {
@@ -75,9 +73,8 @@ module Aromancer
75
73
  def self.sign_out
76
74
  begin
77
75
  Faraday.new(
78
- url: SERVER_URL,
79
- headers: HEADERS,
80
- params: api_key_param
76
+ url: Aromancer::Storage.get_server_url,
77
+ headers: HEADERS.merge(auth_header),
81
78
  ).delete("/session")
82
79
  rescue StandardError => e
83
80
  InternalError.build(e)
@@ -87,9 +84,9 @@ module Aromancer
87
84
  def self.create_legend(params)
88
85
  begin
89
86
  Faraday.new(
90
- url: SERVER_URL,
91
- headers: HEADERS,
92
- params: params.merge(api_key_param)
87
+ url: Aromancer::Storage.get_server_url,
88
+ headers: HEADERS.merge(auth_header),
89
+ params: params
93
90
  ).post("/legends")
94
91
  rescue StandardError => e
95
92
  InternalError.build(e)
@@ -99,21 +96,31 @@ module Aromancer
99
96
  def self.get_legends
100
97
  begin
101
98
  Faraday.new(
102
- url: SERVER_URL,
103
- headers: HEADERS,
104
- params: api_key_param
99
+ url: Aromancer::Storage.get_server_url,
100
+ headers: HEADERS.merge(auth_header),
105
101
  ).get("/legends")
106
102
  rescue StandardError => e
107
103
  InternalError.build(e)
108
104
  end
109
105
  end
110
106
 
107
+ def self.find_legends
108
+ begin
109
+ Faraday.new(
110
+ url: Aromancer::Storage.get_server_url,
111
+ headers: HEADERS.merge(auth_header),
112
+ ).get("/legends/find")
113
+ rescue StandardError => e
114
+ InternalError.build(e)
115
+ end
116
+ end
117
+
111
118
  def self.update_player(params)
112
119
  begin
113
120
  Faraday.new(
114
- url: SERVER_URL,
115
- headers: HEADERS,
116
- params: params.merge(api_key_param)
121
+ url: Aromancer::Storage.get_server_url,
122
+ headers: HEADERS.merge(auth_header),
123
+ params: params
117
124
  ).put("/players/#{Aromancer::Storage.get_player["id"]}")
118
125
  rescue StandardError => e
119
126
  InternalError.build(e)
@@ -121,13 +128,13 @@ module Aromancer
121
128
  end
122
129
 
123
130
  def self.get_arena
124
- legend_id = Aromancer::Storage.get_player["active_legend_id"]
131
+ legend_id = (Aromancer::Storage.get_player || {})["active_legend_id"]
132
+ return InternalError.build(StandardError.new("no active legend")) if legend_id.nil?
125
133
 
126
134
  begin
127
135
  Faraday.new(
128
- url: SERVER_URL,
129
- headers: HEADERS,
130
- params: api_key_param
136
+ url: Aromancer::Storage.get_server_url,
137
+ headers: HEADERS.merge(auth_header),
131
138
  ).get("/legends/#{legend_id}/arena")
132
139
  rescue StandardError => e
133
140
  InternalError.build(e)
@@ -135,13 +142,13 @@ module Aromancer
135
142
  end
136
143
 
137
144
  def self.get_stream
138
- legend_id = Aromancer::Storage.get_player["active_legend_id"]
145
+ legend_id = (Aromancer::Storage.get_player || {})["active_legend_id"]
146
+ return InternalError.build(StandardError.new("no active legend")) if legend_id.nil?
139
147
 
140
148
  begin
141
149
  Faraday.new(
142
- url: SERVER_URL,
143
- headers: HEADERS,
144
- params: api_key_param
150
+ url: Aromancer::Storage.get_server_url,
151
+ headers: HEADERS.merge(auth_header),
145
152
  ).get("/legends/#{legend_id}/stream")
146
153
  rescue StandardError => e
147
154
  InternalError.build(e)
@@ -149,12 +156,13 @@ module Aromancer
149
156
  end
150
157
 
151
158
  def self.get_word_possibilities(selected_word_id)
152
- legend_id = Aromancer::Storage.get_player["active_legend_id"]
159
+ legend_id = (Aromancer::Storage.get_player || {})["active_legend_id"]
160
+ return InternalError.build(StandardError.new("no active legend")) if legend_id.nil?
161
+
153
162
  begin
154
163
  Faraday.new(
155
- url: SERVER_URL,
156
- headers: HEADERS,
157
- params: api_key_param
164
+ url: Aromancer::Storage.get_server_url,
165
+ headers: HEADERS.merge(auth_header),
158
166
  ).get("/legends/#{legend_id}/words/#{selected_word_id}/possibilities")
159
167
  rescue StandardError => e
160
168
  InternalError.build(e)
@@ -162,12 +170,14 @@ module Aromancer
162
170
  end
163
171
 
164
172
  def self.create_turn(params)
165
- legend_id = Aromancer::Storage.get_player["active_legend_id"]
173
+ legend_id = (Aromancer::Storage.get_player || {})["active_legend_id"]
174
+ return InternalError.build(StandardError.new("no active legend")) if legend_id.nil?
175
+
166
176
  begin
167
177
  Faraday.new(
168
- url: SERVER_URL,
169
- headers: HEADERS,
170
- params: params.merge(api_key_param)
178
+ url: Aromancer::Storage.get_server_url,
179
+ headers: HEADERS.merge(auth_header),
180
+ params: params
171
181
  ).post("/legends/#{legend_id}/arena")
172
182
  rescue StandardError => e
173
183
  InternalError.build(e)
@@ -175,12 +185,14 @@ module Aromancer
175
185
  end
176
186
 
177
187
  def self.update_turn(params)
178
- legend_id = Aromancer::Storage.get_player["active_legend_id"]
188
+ legend_id = (Aromancer::Storage.get_player || {})["active_legend_id"]
189
+ return InternalError.build(StandardError.new("no active legend")) if legend_id.nil?
190
+
179
191
  begin
180
192
  Faraday.new(
181
- url: SERVER_URL,
182
- headers: HEADERS,
183
- params: params.merge(api_key_param)
193
+ url: Aromancer::Storage.get_server_url,
194
+ headers: HEADERS.merge(auth_header),
195
+ params: params
184
196
  ).put("/legends/#{legend_id}/arena")
185
197
  rescue StandardError => e
186
198
  InternalError.build(e)
@@ -12,10 +12,20 @@ module Aromancer
12
12
  class Storage
13
13
  include Singleton
14
14
 
15
+ DEFAULT_SERVER = "http://aromancer.com"
16
+
17
+ attr_accessor :storage_open, :cache
18
+
15
19
  def initialize
20
+ clear_cache!
16
21
  FileUtils.mkdir_p(Aromancer.home_directory)
17
22
  end
18
23
 
24
+ def clear_cache!
25
+ @storage_open = false
26
+ @cache = {}
27
+ end
28
+
19
29
  def self.storage_filepath
20
30
  filepath = File.join(Aromancer.home_directory, "storage.json")
21
31
 
@@ -53,6 +63,17 @@ module Aromancer
53
63
  instance.set_key(p_key, value)
54
64
  end
55
65
 
66
+ def self.toggle_preference(key)
67
+ set_preference(key, !get_preference(key))
68
+ end
69
+
70
+ def self.toggle_force_ssl
71
+ toggle_preference(:force_ssl)
72
+ v = get_preference(:force_ssl)
73
+ self.instance.set_key(:server_url_scheme, "http#{v ? :s : ""}")
74
+ self.instance.set_key(:websocket_scheme, "ws#{v ? :s : ""}")
75
+ end
76
+
56
77
  def self.set_signed_out_flag(flag)
57
78
  Aromancer::Storage.instance.set_key(:signed_out_flag, flag)
58
79
  end
@@ -101,20 +122,131 @@ module Aromancer
101
122
  Aromancer::Storage.instance.get_key(:last_error)
102
123
  end
103
124
 
125
+ URI_KEYS = [
126
+ :scheme,
127
+ :host,
128
+ :port
129
+ ]
130
+
131
+ def self.set_server_url(server_url)
132
+ uri = URI.parse(server_url)
133
+ if uri.kind_of?(URI::HTTP) or uri.kind_of?(URI::HTTPS)
134
+ URI_KEYS.each{|k|
135
+ uri.kind_of?(URI::HTTP) && get_preference(:force_ssl) ? toggle_force_ssl : nil
136
+ Aromancer::Storage.instance.set_key("server_url_#{k}", uri.select(k).first)
137
+ }
138
+ elsif get_server_url.nil?
139
+ set_server_url(DEFAULT_SERVER)
140
+ end
141
+ end
142
+
143
+ def self.get_server_url
144
+ set_server_url(DEFAULT_SERVER) if URI_KEYS.all?{|k| Aromancer::Storage.instance.get_key("server_url_#{k}").nil?}
145
+ URI.parse(URI_KEYS.map{|k|
146
+ c = Aromancer::Storage.instance.get_key("server_url_#{k}")
147
+ case k
148
+ when :scheme
149
+ "#{c}://"
150
+ when :host
151
+ c
152
+ when :port
153
+ ":#{c}"
154
+ end
155
+ }.join("")).to_s
156
+ end
157
+
158
+ def self.get_websocket_url
159
+ # default scheme is ws
160
+ w_scheme = Aromancer::Storage.instance.get_key(:websocket_scheme)
161
+ Aromancer::Storage.instance.set_key(:websocket_scheme, :ws) if w_scheme.nil?
162
+ w_scheme = Aromancer::Storage.instance.get_key(:websocket_scheme)
163
+
164
+ # default path is broadcast
165
+ w_path = Aromancer::Storage.instance.get_key(:websocket_path)
166
+ Aromancer::Storage.instance.set_key(:websocket_path, :broadcast) if w_path.nil?
167
+ w_path = Aromancer::Storage.instance.get_key(:websocket_path)
168
+
169
+ # asserts host and port not nil
170
+ # assumes same-as-server host and port for now
171
+ host = Aromancer::Storage.instance.get_key(:server_url_host)
172
+ port = Aromancer::Storage.instance.get_key(:server_url_port)
173
+
174
+ URI.parse("#{w_scheme}://#{host}:#{port}/#{w_path}").to_s
175
+ end
176
+
104
177
  def get_key(key)
105
- storage_contents = JSON.parse(File.read(Aromancer::Storage.storage_filepath))
106
- if storage_contents.keys.include?(key.to_s)
107
- return storage_contents[key.to_s]
178
+ key = key.to_s
179
+
180
+ # ensure default for key exists
181
+ @cache[key] = {value: nil, persisted: false} unless @cache.keys.include?(key)
182
+
183
+ # return cache value if storage open or the cache value is persisted
184
+ return @cache[key][:value] if @storage_open || @cache[key][:persisted]
185
+
186
+ # open storage
187
+ @storage_open = true
188
+ storage_contents = {}
189
+ begin
190
+ # read storage
191
+ storage_contents = JSON.parse(File.read(Aromancer::Storage.storage_filepath))
192
+ rescue StandardError => e
193
+ @storage_open = false
194
+ FileUtils.rm(Aromancer::Storage.storage_filepath)
195
+ return
108
196
  end
197
+
198
+ # only update the cache value if present in storage
199
+ if storage_contents.keys.include?(key)
200
+ @cache[key] = {value: storage_contents[key], persisted: true}
201
+ else
202
+ @cache[key][:persisted] = false
203
+ end
204
+
205
+ @storage_open = false
206
+ @cache[key][:value]
109
207
  end
110
208
 
111
209
  def set_key(key, value)
112
- storage_contents = JSON.parse(File.read(Aromancer::Storage.storage_filepath))
113
- storage_contents[key.to_s] = value
210
+ key = key.to_s
211
+
212
+ # ensure default for key exists
213
+ @cache[key] = {value: nil, persisted: false} unless @cache.keys.include?(key)
214
+
215
+ # assign new cache value and set persisted to false
216
+ @cache[key][:value] = value
217
+ @cache[key][:persisted] = false
218
+
219
+ # return if storage open
220
+ # non-persisted cache values update next cycle
221
+ return if @storage_open
222
+
223
+ # open storage
224
+ @storage_open = true
225
+ storage_contents = {}
226
+ begin
227
+ # read storage
228
+ storage_contents = JSON.parse(File.read(Aromancer::Storage.storage_filepath))
229
+ rescue StandardError => e
230
+ @storage_open = false
231
+ FileUtils.rm(Aromancer::Storage.storage_filepath)
232
+ return
233
+ end
234
+
235
+ # update storage and cache
236
+ ([key] + @cache.keys.select{|k|
237
+ !@cache[k][:persisted]
238
+ }).each{|k|
239
+ storage_contents[k] = @cache[k][:value]
240
+ @cache[k][:persisted] = true
241
+ }
114
242
 
243
+ # write storage
115
244
  File.open(Aromancer::Storage.storage_filepath, "w+") do |f|
116
245
  f.write(storage_contents.to_json)
117
246
  end
247
+
248
+ # close storage
249
+ @storage_open = false
118
250
  end
119
251
  end
120
252
  end
@@ -11,5 +11,5 @@
11
11
  =end
12
12
 
13
13
  module Aromancer
14
- VERSION = "0.0.4"
14
+ VERSION = "0.0.6"
15
15
  end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ =begin
4
+
5
+ websocket.rb
6
+
7
+ websocket client.
8
+
9
+ by i2097i
10
+
11
+ =end
12
+
13
+ module Aromancer
14
+ class Websocket
15
+ include Singleton
16
+
17
+ TYPES = [
18
+ :arena,
19
+ :legends,
20
+ :player # todo: not used
21
+ ]
22
+
23
+ attr_accessor :socket
24
+
25
+ def connect
26
+ return unless self.socket.nil?
27
+ return if Aromancer::Storage.get_player["api_key"].nil?
28
+
29
+ begin
30
+ self.socket = WebSocket::Client::Simple.connect(Aromancer::Storage.get_websocket_url, {headers: Aromancer::Server.auth_header})
31
+ rescue Socket::ResolutionError => e
32
+ disconnect
33
+ return
34
+ end
35
+ self.socket.on :message do |msg|
36
+ payload = JSON.parse(msg.data) rescue msg.data
37
+ if !payload.nil? &&
38
+ !payload["identifier"].nil? &&
39
+ !payload["message"].nil? &&
40
+ !payload["message"]["type"].nil? &&
41
+ TYPES.include?(payload["message"]["type"].to_sym)
42
+
43
+ case payload["message"]["type"].to_sym
44
+ when :arena
45
+ legend_id = payload["message"]["id"]
46
+ lid = Aromancer::Storage.get_player&.symbolize_keys[:active_legend_id]
47
+ if !lid.nil? && lid == legend_id.to_i
48
+ Aromancer::Api.load_arena
49
+ sleep(0.5)
50
+ Aromancer::Cli.instance.start_ui_thread
51
+ end
52
+ when :legends
53
+ Aromancer::Api.get_legends
54
+ when :player
55
+ end
56
+ end
57
+ end
58
+
59
+ self.socket.on :open do
60
+ r = {
61
+ command: "subscribe",
62
+ identifier: {channel: "PlayerChannel"}.to_json
63
+ }.to_json
64
+ Aromancer::Websocket.instance.socket.send(r)
65
+ end
66
+
67
+ self.socket.on :close do |e|
68
+ Aromancer::Websocket.instance.disconnect
69
+ end
70
+
71
+ self.socket.on :error do |e|
72
+ puts e.inspect
73
+ Aromancer::Websocket.instance.disconnect
74
+ end
75
+ end
76
+
77
+ def disconnect
78
+ self.socket&.close
79
+ self.socket = nil
80
+ end
81
+ end
82
+ end
data/lib/aromancer.rb CHANGED
@@ -14,8 +14,116 @@ require_relative :reiquire.to_s
14
14
  Reiquire::aromancer
15
15
 
16
16
  module Aromancer
17
+ THROTTLE_MS = 0.2
17
18
  FONT = TTY::Font.new(:standard)
18
19
 
20
+ UNICODE = {
21
+ close: "\u2A2F",
22
+ check: "\u2713"
23
+ }
24
+
25
+ # instance runs on main thread
26
+ class Cli
27
+ include Singleton
28
+
29
+ attr_accessor :ui_thread, :ws_thread, :busy, :last_render
30
+
31
+ # user entry point
32
+ def self.run
33
+ # ensure preferences exist
34
+ Aromancer::Storage.get_preference(:debug, default: false)
35
+ Aromancer::Storage.get_preference(:force_ssl, default: true)
36
+ Aromancer::Storage.get_preference(:hide_sentences, default: true)
37
+
38
+ self.instance.last_render = 0
39
+
40
+ # load player on-start
41
+ Aromancer::Api.set_active_legend_id(nil) unless Aromancer::Storage.get_player.nil?
42
+
43
+ Thread.main[:route] = :authentication
44
+ Thread.main[:menu_options] = {}
45
+ Aromancer::Prompt.say(I18n.t("shared.welcome", version: Aromancer::VERSION))
46
+ Aromancer::Prompt.say(I18n.t("shared.server_url", url: Aromancer::Storage::get_server_url))
47
+ self.instance.start_ui_thread
48
+ self.instance.start_ws_thread
49
+ loop do
50
+ begin
51
+ self.instance.ui_thread.join
52
+ rescue NoMethodError => e
53
+ if Aromancer::Storage.get_preference(:debug)
54
+ Aromancer::Prompt.say(I18n.t("shared.last_error"))
55
+ Aromancer::Prompt.say(e.inspect)
56
+ sleep(5)
57
+ end
58
+ self.instance.start_ui_thread
59
+ self.instance.start_ws_thread
60
+ sleep(1)
61
+ rescue Interrupt => e
62
+ exit
63
+ end
64
+ end
65
+ end
66
+
67
+ def get_view(view_key)
68
+ Thread.main[:menu_options][view_key.to_sym]
69
+ end
70
+
71
+ def start_ui_thread(force = false)
72
+ return if (@busy || Time.now - @last_render < 2) && !force
73
+
74
+ @busy = true
75
+ self.ui_thread&.exit
76
+ self.ui_thread&.kill
77
+ Thread.main[:menu_runners] = {}
78
+ Aromancer::Prompt.instance.reload!
79
+ self.ui_thread = Thread.new do
80
+ loop do
81
+ menu_options = Thread.main[:menu_options][Thread.main[:route]]
82
+ menu_runners = Thread.main[:menu_runners][Thread.main[:route]]
83
+ unless menu_options.nil? || menu_runners.nil?
84
+ Aromancer::Routes.resume
85
+ sleep(THROTTLE_MS)
86
+ Aromancer.print_hud
87
+ v = Aromancer::Prompt.p.select("", menu_options, show_help: :always, cycle: true, per_page: 11)
88
+ runners = menu_runners.select{|r| r[:value] == v}
89
+ runners = menu_runners.select{|r| r[:value].nil?} if runners.empty?
90
+
91
+ if runners.any?
92
+ r = runners.first
93
+ r[:proc].call(v)
94
+ else
95
+ exit
96
+ end
97
+ else
98
+ Aromancer::Routes.resume
99
+ end
100
+ Aromancer::Cli.instance.busy = false
101
+ Aromancer::Cli.instance.last_render = Time.now
102
+ # sleep(THROTTLE_MS)
103
+ end
104
+ end
105
+ end
106
+
107
+ def start_ws_thread
108
+ self.ws_thread.kill unless self.ws_thread.nil?
109
+ self.ws_thread = Thread.new do
110
+ Aromancer::Websocket.instance.disconnect
111
+ loop do
112
+ player = Aromancer::Storage.get_player
113
+ if player.nil?
114
+ Aromancer::Websocket.instance.disconnect
115
+ elsif !player["api_key"].nil?
116
+ # attempt websocket connection
117
+ Aromancer::Websocket.instance.connect
118
+ end
119
+
120
+ sleep(4)
121
+ end
122
+ end
123
+ end
124
+
125
+ end
126
+
19
127
  def self.get_font(text = :aromancer.to_s, letter_spacing = 1)
20
128
  Aromancer::FONT.write(text, letter_spacing: letter_spacing).split("\n")
21
129
  end
@@ -24,6 +132,19 @@ module Aromancer
24
132
  "#{`echo $HOME`.strip}/.aromancer"
25
133
  end
26
134
 
135
+ def self.print_hud
136
+ websocket_status = "#{Aromancer::Storage.instance.get_key(:websocket_scheme)}: #{Aromancer::Websocket.instance.socket.nil? ? UNICODE[:close] : UNICODE[:check] }"
137
+ player_status = if Aromancer::Storage.get_player.nil?
138
+ I18n.t("routes.main_menu.not_signed_in")
139
+ else
140
+ I18n.t("shared.signed_in_as", as: Aromancer::Storage.get_player["alias"])
141
+ end
142
+
143
+ Aromancer::Prompt.say(
144
+ [nil, player_status, websocket_status, nil].join(" | ")
145
+ )
146
+ end
147
+
27
148
  def self.print_last_error(i18n_key = nil)
28
149
  last_error = Aromancer::Storage.get_last_error
29
150
  return if last_error.nil?
@@ -35,6 +156,7 @@ module Aromancer
35
156
  rescue JSON::ParserError => e
36
157
  message = last_error[:body]
37
158
  end
38
- Aromancer::Prompt.say(I18n.t(i18n_key, status: last_error[:status], error: message))
159
+ Aromancer::Prompt.say(I18n.t(i18n_key || "shared.last_error", status: last_error[:status], error: message))
160
+ Aromancer::Storage.set_last_error(nil)
39
161
  end
40
162
  end
data/lib/reiquire.rb CHANGED
@@ -13,7 +13,7 @@ require :"active_support/all".to_s
13
13
  require :i18n.to_s
14
14
  require :faraday.to_s
15
15
  require :fileutils.to_s
16
- # require :"websocket-client-simple".to_s
16
+ require :"websocket-client-simple".to_s
17
17
 
18
18
  # require :"tty-box".to_s # draw box shapes
19
19
  # require :"tty-color".to_s # detect color support
data/locale/en.yml CHANGED
@@ -7,7 +7,13 @@ en:
7
7
  inturrupt: "exiting due to user inturrupt..."
8
8
  reader_initialized: "input reader initialized."
9
9
  welcome: "welcome to aromancer. loading v%{version}..."
10
+ server_url: "current server: %{url}"
10
11
  navigate: "navigate to %{route}."
12
+ update_cancel: "update cancelled."
13
+ update_confirm: "confirm update [%{new_value}] (enter y): "
14
+ signed_in_as: "authenticated as %{as}"
15
+ last_error: "error (%{status}). %{error}"
16
+ back: "back"
11
17
  exit: "exit"
12
18
  routes:
13
19
  authentication:
@@ -17,17 +23,31 @@ en:
17
23
  title: "authenticate"
18
24
  subtitle: ""
19
25
  main_menu:
26
+ configure: "configure"
20
27
  title: "main menu"
21
28
  subtitle: ""
22
29
  new_game: "new game"
23
30
  load_game: "load game"
31
+ find_game: "find game"
32
+ player_count: "enter player count (1-2): "
24
33
  sign_out: "sign out"
25
- not_signed_in: "not signed in. invalid session. redirecting to authentication."
34
+ not_signed_in: "signed out"
35
+ configure:
36
+ title: "configure %{name}"
37
+ new_host: "enter new server host: "
38
+ new_port: "enter new server port: "
39
+ force_ssl_toggle: "ssl preference toggled to %{value}"
40
+ server_host: "host (%{stored})"
41
+ server_port: "port (%{stored})"
42
+ force_ssl: "ssl (%{stored})"
43
+ debug: "debug (%{stored})"
44
+ debug_toggle: "debug preference toggled to %{value}"
26
45
  new_game:
27
46
  player_count: "number of players"
28
47
  story_count: "number of stories"
29
48
  starting: "starting new game..."
30
49
  status: "status"
50
+ cancelled: "cancelled. game not created."
31
51
  legend:
32
52
  title: "legend"
33
53
  subtitle: "%{subtitle}"