locallingo 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +66 -0
- data/README.md +106 -1
- data/config/locallingo.default.yml +6 -0
- data/lib/locallingo/active_record.rb +167 -0
- data/lib/locallingo/batch_translator.rb +26 -3
- data/lib/locallingo/configuration.rb +40 -18
- data/lib/locallingo/content_leaves.rb +40 -0
- data/lib/locallingo/drift.rb +93 -0
- data/lib/locallingo/manager.rb +16 -59
- data/lib/locallingo/prompt.rb +73 -0
- data/lib/locallingo/providers/ruby_llm.rb +43 -26
- data/lib/locallingo/quality/static_rules.rb +6 -5
- data/lib/locallingo/quality/terminology.rb +1 -1
- data/lib/locallingo/record_translator.rb +94 -0
- data/lib/locallingo/rubocop.rb +1 -1
- data/lib/locallingo/settings.rb +12 -1
- data/lib/locallingo/translator.rb +70 -0
- data/lib/locallingo/validators/manual_edits.rb +4 -8
- data/lib/locallingo/validators/outdated.rb +5 -5
- data/lib/locallingo/version.rb +1 -1
- data/lib/locallingo.rb +50 -6
- metadata +7 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../
|
|
3
|
+
require_relative "../drift"
|
|
4
4
|
|
|
5
5
|
module Locallingo
|
|
6
6
|
module Validators
|
|
@@ -12,19 +12,15 @@ module Locallingo
|
|
|
12
12
|
# it was edited by a human — surface it so the operator can protect it with
|
|
13
13
|
# `accept-edits` before the next translate run overwrites it.
|
|
14
14
|
class ManualEdits
|
|
15
|
-
def initialize(cli_name: "lingo")
|
|
15
|
+
def initialize(cli_name: "lingo", drift: Drift.new)
|
|
16
16
|
@cli_name = cli_name
|
|
17
|
+
@drift = drift
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
# +target+ is the flat target hash; +locale_state+ its loaded state.
|
|
20
21
|
def call(target:, locale_state:, locale:)
|
|
21
22
|
target.filter_map do |key, value|
|
|
22
|
-
entry
|
|
23
|
-
next unless entry.is_a?(Hash)
|
|
24
|
-
next if entry["manual"]
|
|
25
|
-
|
|
26
|
-
stored = entry["target_hash"]
|
|
27
|
-
next unless stored && stored != StateStore.hash(value)
|
|
23
|
+
next unless @drift.hand_edited?(target: value, entry: locale_state[key])
|
|
28
24
|
|
|
29
25
|
{
|
|
30
26
|
type: :manual_edit,
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../
|
|
3
|
+
require_relative "../drift"
|
|
4
4
|
|
|
5
5
|
module Locallingo
|
|
6
6
|
module Validators
|
|
7
7
|
# Reports target keys whose recorded source hash no longer matches the
|
|
8
8
|
# current source value — i.e. the English text changed after translation.
|
|
9
9
|
class Outdated
|
|
10
|
-
def initialize(cli_name: "lingo")
|
|
10
|
+
def initialize(cli_name: "lingo", drift: Drift.new)
|
|
11
11
|
@cli_name = cli_name
|
|
12
|
+
@drift = drift
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
# +source+ is the flat source hash; +locale_state+ is the target locale's
|
|
@@ -27,8 +28,7 @@ module Locallingo
|
|
|
27
28
|
# Keys whose stored source_hash differs from the current source hash.
|
|
28
29
|
def outdated_keys(source, locale_state)
|
|
29
30
|
source.filter_map do |key, value|
|
|
30
|
-
|
|
31
|
-
key if stored && stored != StateStore.hash(value)
|
|
31
|
+
key if @drift.outdated?(source: value, entry: locale_state[key])
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -37,7 +37,7 @@ module Locallingo
|
|
|
37
37
|
# Manual keys must not be pushed toward machine retranslation — the value
|
|
38
38
|
# is hand-curated, so the human updates it and re-accepts.
|
|
39
39
|
def suggestion_for(key, locale, locale_state)
|
|
40
|
-
if
|
|
40
|
+
if @drift.manual?(locale_state[key])
|
|
41
41
|
"Source changed for a manually-curated key. Update the #{locale} value by hand, " \
|
|
42
42
|
"then run: #{@cli_name} accept-edits --locale #{locale} --key #{key}"
|
|
43
43
|
else
|
data/lib/locallingo/version.rb
CHANGED
data/lib/locallingo.rb
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "monitor"
|
|
3
4
|
require_relative "locallingo/version"
|
|
4
5
|
require_relative "locallingo/settings"
|
|
5
6
|
require_relative "locallingo/configuration"
|
|
6
7
|
require_relative "locallingo/json_extraction"
|
|
7
8
|
require_relative "locallingo/key_flattener"
|
|
8
9
|
require_relative "locallingo/state_store"
|
|
10
|
+
require_relative "locallingo/drift"
|
|
9
11
|
require_relative "locallingo/providers/ruby_llm"
|
|
10
12
|
require_relative "locallingo/batch_translator"
|
|
13
|
+
require_relative "locallingo/prompt"
|
|
14
|
+
require_relative "locallingo/translator"
|
|
15
|
+
require_relative "locallingo/record_translator"
|
|
11
16
|
require_relative "locallingo/validators/missing"
|
|
12
17
|
require_relative "locallingo/validators/outdated"
|
|
13
18
|
require_relative "locallingo/validators/duplicate_values"
|
|
@@ -38,20 +43,59 @@ module Locallingo
|
|
|
38
43
|
Configuration.load(root_path:, package:)
|
|
39
44
|
end
|
|
40
45
|
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
# Boot-time state, finalized: always a frozen Settings, so any thread or
|
|
47
|
+
# Ractor reads finished settings and only `configure` replaces them. With
|
|
48
|
+
# String keys (or none) it is Ractor-shareable; a callable key is not, so it
|
|
49
|
+
# keeps the settings readable on the main Ractor only.
|
|
50
|
+
#
|
|
51
|
+
# rubocop:disable ThreadSafety/ClassInstanceVariable -- @settings is written only by configure and reset_settings!, at boot on the main Ractor, and always holds a frozen Settings; @configure_lock (engines without Ractor only) is set once, at load
|
|
52
|
+
@settings = Settings.new.freeze
|
|
53
|
+
|
|
54
|
+
# Serializes configure: one Monitor per Ractor, in Ractor-local storage (only
|
|
55
|
+
# the main Ractor can publish @settings, so in practice the main Ractor's).
|
|
56
|
+
# The main Ractor's is made here, at load, never lazily. A Monitor, so a
|
|
57
|
+
# configure nested in a configure block does not deadlock. Engines without
|
|
58
|
+
# Ractor keep it in @configure_lock.
|
|
59
|
+
if defined?(::Ractor)
|
|
60
|
+
::Ractor.current[:locallingo_configure_lock] = Monitor.new
|
|
61
|
+
else
|
|
62
|
+
@configure_lock = Monitor.new
|
|
44
63
|
end
|
|
45
64
|
|
|
65
|
+
# Code-level settings (provider credentials) — see Locallingo::Settings.
|
|
66
|
+
def self.settings = @settings
|
|
67
|
+
|
|
46
68
|
# Configure the gem from Ruby — the credentials path for apps whose keys
|
|
47
69
|
# don't live in ENV (call it from a Rails initializer or a `.locallingo.rb`
|
|
48
70
|
# setup file next to `.locallingo.yml`; the CLI loads the latter on start).
|
|
71
|
+
# Yields a copy of the current settings, then publishes it frozen in one
|
|
72
|
+
# assignment: a concurrent reader sees the old settings or the new, never
|
|
73
|
+
# half of them. Concurrent calls run one at a time, so neither drops the
|
|
74
|
+
# other's keys. Calling it again keeps the keys it does not set.
|
|
49
75
|
def self.configure
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
configure_lock.synchronize do
|
|
77
|
+
draft = settings.dup
|
|
78
|
+
yield draft
|
|
79
|
+
@settings = draft.freeze
|
|
80
|
+
end
|
|
52
81
|
end
|
|
53
82
|
|
|
83
|
+
# Back to frozen, blank defaults (for tests).
|
|
54
84
|
def self.reset_settings!
|
|
55
|
-
@settings =
|
|
85
|
+
@settings = Settings.new.freeze
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Another Ractor's lock is made on first use: atomically on Ruby 3.4+; below,
|
|
89
|
+
# two of its threads could each make one, which is harmless because a
|
|
90
|
+
# non-main Ractor cannot publish settings anyway.
|
|
91
|
+
def self.configure_lock
|
|
92
|
+
return @configure_lock unless defined?(::Ractor)
|
|
93
|
+
|
|
94
|
+
key = :locallingo_configure_lock
|
|
95
|
+
return ::Ractor.store_if_absent(key) { Monitor.new } if ::Ractor.respond_to?(:store_if_absent)
|
|
96
|
+
|
|
97
|
+
::Ractor.current[key] ||= Monitor.new
|
|
56
98
|
end
|
|
99
|
+
private_class_method :configure_lock
|
|
100
|
+
# rubocop:enable ThreadSafety/ClassInstanceVariable
|
|
57
101
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: locallingo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -69,21 +69,27 @@ files:
|
|
|
69
69
|
- config/locallingo.default.yml
|
|
70
70
|
- exe/lingo
|
|
71
71
|
- lib/locallingo.rb
|
|
72
|
+
- lib/locallingo/active_record.rb
|
|
72
73
|
- lib/locallingo/batch_translator.rb
|
|
73
74
|
- lib/locallingo/cli.rb
|
|
74
75
|
- lib/locallingo/configuration.rb
|
|
76
|
+
- lib/locallingo/content_leaves.rb
|
|
77
|
+
- lib/locallingo/drift.rb
|
|
75
78
|
- lib/locallingo/json_extraction.rb
|
|
76
79
|
- lib/locallingo/key_flattener.rb
|
|
77
80
|
- lib/locallingo/manager.rb
|
|
81
|
+
- lib/locallingo/prompt.rb
|
|
78
82
|
- lib/locallingo/providers/ruby_llm.rb
|
|
79
83
|
- lib/locallingo/quality/british_spellings.rb
|
|
80
84
|
- lib/locallingo/quality/static_rules.rb
|
|
81
85
|
- lib/locallingo/quality/terminology.rb
|
|
82
86
|
- lib/locallingo/quality_checker.rb
|
|
87
|
+
- lib/locallingo/record_translator.rb
|
|
83
88
|
- lib/locallingo/reporter.rb
|
|
84
89
|
- lib/locallingo/rubocop.rb
|
|
85
90
|
- lib/locallingo/settings.rb
|
|
86
91
|
- lib/locallingo/state_store.rb
|
|
92
|
+
- lib/locallingo/translator.rb
|
|
87
93
|
- lib/locallingo/validators/duplicate_values.rb
|
|
88
94
|
- lib/locallingo/validators/manual_edits.rb
|
|
89
95
|
- lib/locallingo/validators/missing.rb
|