Skip to content

Commit e90c40f

Browse files
iHiDampcode-comdem4ronclaude
authored
Add a glossary (#8166)
* WIP * Amp Improvemnts (#8186) * Amp Improvemnts * Fix glossary entry proposal command signatures - Update Approve and Reject commands to accept proposal directly instead of glossary_entry - Fix controller to pass @proposal to Approve/Reject commands - Update test expectations to match corrected command signatures - Resolves logical inconsistency where commands tried to find proposals through glossary entries Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-b0f4fc46-b5d8-47f8-899f-b9cab8f38b5f --------- Co-authored-by: Amp <amp@ampcode.com> * Add Glossary entries UI (#8189) * Add Glossary entries UI * Fix types * Tiny adjustment * Add i18n glossary and reserved terms files - Add i18n_GLOSSARY.tsv with 45 key Exercism terms and translation guidance - Add i18n_RESERVED.tsv with brand names and technical terms that should not be translated - Update .gitignore to exclude .claude/ directory These files will help ensure consistent translations across languages by providing clear guidance on how terms should be translated and which terms should remain in English. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Add additional terms to i18n glossary Added 23 more important terms including: - Organization terms: open-source, not-for-profit, donate, donation, supporter - Membership terms: Insiders, perks, favorites, member, premium - Programming terms: coding, programming, developer, code - Payment terms: subscription, monthly, recurring, payment - Achievement terms: level, tier, reward, achievement, milestone These additions provide more comprehensive translation guidance for commonly used Exercism terminology. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Sort glossary and reserved terms alphabetically Both TSV files are now sorted alphabetically by the first column (term name) while preserving the header row. This makes it easier to find and maintain terms. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix typo * Add propose-term modal (#8193) * Start adding modal * Add modal * Load terms into db, use that in react_helper * Add terms to i18n glossary Added: administrator, argument, bootcamp, Contribute, Discover, function, generator, Learn, method, parameter, queue Also removed duplicate tenses and plurals (submitted, submitting, solutions) as they don't add value to the glossary. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Add glossary translation infrastructure and Hungarian translations - Created instructions for LLMs to generate glossary translations (scripts/llm_instructions/create_glossary_file.md) - Generated Hungarian glossary translations (i18n/glossary/hu.tsv) with 82 terms - Created import script (scripts/import_glossary_translations.rb) to load translations into database - Script parses TSV files and uses Localization::GlossaryEntry::Create service - Successfully imported all Hungarian translations to database - Fixed debug statement in glossary_entries_list helper 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Add create method to API::Localization::GlossaryEntriesController - Add create action that proxies to Localization::GlossaryEntry::Create - Accept parameters via params[:glossary_entry] for locale, term, translation, and llm_instructions - Return empty JSON with 201 status on success - Add corresponding route and tests 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Add German, Dutch, and Polish glossary translations and reorganize i18n files - Created glossary translations for German (de), Dutch (nl), and Polish (pl) - Each translation file contains all 82 terms with appropriate translations - Successfully imported all translations to database (328 total entries) - Moved base glossary and reserved words files to i18n/ folder with clearer names: - i18n_GLOSSARY.tsv -> i18n/glossary_base.tsv - i18n_RESERVED.tsv -> i18n/reserved_words.tsv - Updated scripts to reference new file locations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Glossary-UI adjustments (#8202) * Adjust table * Glossary show tweaks * Comment out uuid * Fix schema * Update glossary entry proposal to set status to :proposed - Modified CreateModification to set glossary_entry status to :proposed - Added destroy method to GlossaryEntriesController for deletion proposals - Fixed tests to match updated command signatures and behaviors - Added missing existing_locales method to TranslateToAllLocales - Fixed test isolation issues and made tests more robust 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix test isolation issues in localization command tests - Replace define_singleton_method with proper Mocha stubs to fix test isolation - Add explicit stubs only in tests that don't verify LLM calls - Add ordering to glossary entry search for consistent results - All localization command tests now pass reliably 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Edit mode tweaks * Clean up glossary-entries `show` * Refactor * No glossary entries found * Add LocaleSelect * Add All option * Add min width * Tweaks * Update GlossaryEntryProposal commands to manage status and add Search tests - Update GlossaryEntryProposal::Approve to set glossary_entry status to :checked - Update GlossaryEntryProposal::Reject to set status to :unchecked when no pending proposals remain - Add locale parameter support to GlossaryEntry::Search - Create comprehensive test suite for GlossaryEntry::Search following Original::SearchTest pattern - Update assembler to pass filter_locale parameter to Search command 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Tweak proposal modal, and LocaleSelect-or (#8204) * Tweaks * Add guards, API next action, and fix method naming for glossary entry management - Add may_manage_translation_proposals? method to User model (reputation > 20) - Add guards to GlossaryEntryProposal commands (Approve, Reject, UpdateValue) - Implement next action in API::Localization::GlossaryEntriesController - Add route and comprehensive tests for API next action - Fix locale handling in GlossaryEntry::Search (convert symbols to strings) - Update tests to use data.update! instead of stubs for translator_locales - Add translator edit view and controller actions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: dem4ron <demaaron88@gmail.com>
1 parent d287d77 commit e90c40f

92 files changed

Lines changed: 3712 additions & 38 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,5 @@ config/settings.local.yml
5959
app/javascript/i18n/.env
6060
vendor/bundle/
6161

62-
# Claude local settings
62+
# Claude files
6363
.claude/

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ rm -rf .built-assets/ # Clear asset cache if needed
116116
4. Add appropriate tests (see testing docs)
117117
5. Run pre-commit validation commands
118118

119+
**Code Style:**
120+
121+
- **Always use symbol key syntax (`key:`) in hashes** - Use `{ name: "value" }` not `{ "name" => "value" }` unless there's a specific reason to use string keys (like when the key contains special characters or spaces)
122+
- This applies to serializers, API responses, and all Ruby hash structures
123+
119124
## Reference Documentation
120125

121126
- `docs/context/overview.md` - Complete codebase documentation and setup guide
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
class AssembleLocalizationGlossaryEntries
2+
include Mandate
3+
4+
initialize_with :user, :params
5+
6+
def self.keys
7+
%i[criteria status page]
8+
end
9+
10+
def call
11+
SerializePaginatedCollection.(
12+
glossary_entries,
13+
serializer: SerializeLocalizationGlossaryEntries,
14+
serializer_args: user,
15+
meta: {
16+
unscoped_total: Localization::GlossaryEntry.count
17+
}
18+
)
19+
end
20+
21+
memoize
22+
def glossary_entries
23+
Localization::GlossaryEntry::Search.(
24+
user,
25+
criteria: params[:criteria],
26+
status: params[:status],
27+
page: params[:page],
28+
locale: params[:filter_locale]
29+
)
30+
end
31+
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class Localization::GlossaryEntry::Create
2+
include Mandate
3+
4+
initialize_with :user, :locale, :term, :translation, :llm_instructions
5+
6+
def call
7+
guard!
8+
9+
Localization::GlossaryEntry.create!(
10+
locale:,
11+
term:,
12+
translation:,
13+
llm_instructions:
14+
)
15+
rescue ActiveRecord::RecordNotUnique
16+
Localization::GlossaryEntry.find_by!(locale:, term:)
17+
end
18+
19+
def guard!
20+
return if user.may_create_translation_proposals?
21+
22+
raise "You need to have at least 10 rep to create glossary entries"
23+
end
24+
end
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
class Localization::GlossaryEntry::Search
2+
include Mandate
3+
4+
DEFAULT_PAGE = 1
5+
DEFAULT_PER = 24
6+
7+
def self.default_per
8+
DEFAULT_PER
9+
end
10+
11+
def initialize(user, page: nil, per: nil, criteria: nil, status: nil, locale: nil)
12+
@user = user
13+
14+
@criteria = criteria
15+
@status = status
16+
@locale = locale
17+
@page = page.present? && page.to_i.positive? ? page.to_i : DEFAULT_PAGE
18+
@per = per.present? && per.to_i.positive? ? per.to_i : self.class.default_per
19+
end
20+
21+
def call
22+
@glossary_entries = Localization::GlossaryEntry.where(locale: locales)
23+
24+
filter_criteria!
25+
filter_status!
26+
filter_locale!
27+
28+
paginated_glossary_entries = @glossary_entries.
29+
order(:locale, :term).
30+
page(page).per(per)
31+
32+
Kaminari.paginate_array(
33+
paginated_glossary_entries,
34+
total_count: @glossary_entries.count
35+
).page(page).per(per)
36+
end
37+
38+
memoize
39+
def locales = (user.translator_locales - [:en]).map(&:to_s)
40+
41+
private
42+
attr_reader :user, :per, :page, :criteria, :status, :locale
43+
44+
def filter_criteria!
45+
return if criteria.blank?
46+
47+
@glossary_entries = @glossary_entries.where(
48+
"localization_glossary_entries.term LIKE ? OR localization_glossary_entries.translation LIKE ?",
49+
"%#{criteria}%", "%#{criteria}%"
50+
)
51+
end
52+
53+
def filter_status!
54+
return if status.blank?
55+
56+
@glossary_entries = @glossary_entries.where("localization_glossary_entries.status": status)
57+
end
58+
59+
def filter_locale!
60+
return if locale.blank?
61+
62+
@glossary_entries = @glossary_entries.where("localization_glossary_entries.locale": locale)
63+
end
64+
end
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
class Localization::GlossaryEntryProposal::Approve
2+
include Mandate
3+
4+
initialize_with :proposal, :user
5+
6+
def call
7+
guard!
8+
9+
ActiveRecord::Base.transaction do
10+
proposal.update!(
11+
status: :approved,
12+
reviewer: user
13+
)
14+
handle_glossary_update!
15+
end
16+
end
17+
18+
private
19+
def guard!
20+
return if user.may_manage_translation_proposals?
21+
22+
raise "You need to have at least 20 rep to approve glossary entries"
23+
end
24+
25+
def handle_glossary_update!
26+
send("handle_#{proposal.type.to_s.camelize(:lower)}!")
27+
end
28+
29+
def handle_addition!
30+
raise "Glossary entry already exists" if Localization::GlossaryEntry.exists?(locale: proposal.locale, term: proposal.term)
31+
32+
Localization::GlossaryEntry.create!(
33+
locale: proposal.locale,
34+
term: proposal.term,
35+
translation: proposal.translation,
36+
llm_instructions: proposal.llm_instructions,
37+
status: :checked
38+
)
39+
end
40+
41+
def handle_modification!
42+
raise "No glossary entry to modify" if proposal.glossary_entry.nil?
43+
44+
proposal.glossary_entry.update!(
45+
translation: proposal.translation,
46+
llm_instructions: proposal.llm_instructions,
47+
status: :checked
48+
)
49+
end
50+
51+
def handle_deletion!
52+
raise "No glossary entry to delete" if proposal.glossary_entry.nil?
53+
54+
# Retrieve this before destroying else we have a race
55+
glossary_entry = proposal.glossary_entry
56+
57+
# Disassociate before deletion to avoid FK issues
58+
proposal.update!(glossary_entry: nil)
59+
glossary_entry.destroy!
60+
end
61+
end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class Localization::GlossaryEntryProposal
2+
class CreateAddition
3+
include Mandate
4+
5+
initialize_with :term, :locale, :user, :translation, :llm_instructions
6+
7+
def call
8+
ActiveRecord::Base.transaction do
9+
Localization::GlossaryEntryProposal.create!(
10+
type: :addition,
11+
proposer: user,
12+
term: term,
13+
locale: locale,
14+
translation: translation,
15+
llm_instructions: llm_instructions
16+
)
17+
end.tap do |proposal| # rubocop:disable Style/MultilineBlockChain
18+
VerifyWithLLM.defer(proposal)
19+
end
20+
end
21+
end
22+
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class Localization::GlossaryEntryProposal
2+
class CreateDeletion
3+
include Mandate
4+
5+
initialize_with :glossary_entry, :user
6+
7+
def call
8+
ActiveRecord::Base.transaction do
9+
Localization::GlossaryEntryProposal.create!(
10+
type: :deletion,
11+
glossary_entry: glossary_entry,
12+
proposer: user
13+
)
14+
end
15+
end
16+
end
17+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class Localization::GlossaryEntryProposal
2+
class CreateModification
3+
include Mandate
4+
5+
initialize_with :glossary_entry, :user, :translation
6+
7+
def call
8+
ActiveRecord::Base.transaction do
9+
glossary_entry.update!(status: :proposed)
10+
Localization::GlossaryEntryProposal.create!(
11+
type: :modification,
12+
glossary_entry: glossary_entry,
13+
proposer: user,
14+
translation: translation
15+
)
16+
end.tap do |proposal| # rubocop:disable Style/MultilineBlockChain
17+
VerifyWithLLM.defer(proposal)
18+
end
19+
end
20+
end
21+
end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
class Localization::GlossaryEntryProposal::Reject
2+
include Mandate
3+
4+
initialize_with :proposal, :user
5+
6+
def call
7+
guard!
8+
9+
ActiveRecord::Base.transaction do
10+
proposal.update!(
11+
status: :rejected,
12+
reviewer: user
13+
)
14+
15+
# Set glossary_entry status to unchecked if no pending proposals remain (for modifications only)
16+
if proposal.type == :modification && proposal.glossary_entry
17+
glossary_entry = proposal.glossary_entry
18+
glossary_entry.update!(status: :unchecked) unless glossary_entry.proposals.pending.exists?
19+
end
20+
end
21+
end
22+
23+
private
24+
def guard!
25+
return if user.may_manage_translation_proposals?
26+
27+
raise "You need to have at least 20 rep to reject glossary entries"
28+
end
29+
end

0 commit comments

Comments
 (0)