Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
799d8f9
Add locales (#8107)
iHiD Aug 26, 2025
d962b40
WIP on dashboard
iHiD Aug 26, 2025
9fa60a0
Move translation files into database
iHiD Aug 26, 2025
326da54
Add dashboard
iHiD Aug 27, 2025
3fb9d35
Try and fix CI
iHiD Aug 27, 2025
9669c32
Describe i18n keys (#8157)
dem4ron Aug 27, 2025
305a9be
Add LLM retries
iHiD Aug 27, 2025
00b3012
Remove temporary override
iHiD Aug 28, 2025
ec77585
Fix schema
iHiD Aug 28, 2025
86c9028
Translate to all languages
iHiD Aug 28, 2025
61e00e8
Use db-only for HAML
iHiD Aug 28, 2025
fb47f19
Improve translation
dem4ron Sep 5, 2025
88e77f3
Comment out nonsense
dem4ron Sep 5, 2025
5bbfb7c
Load locales into db each time
iHiD Sep 5, 2025
d287d77
FIX JS tests
dem4ron Sep 5, 2025
e90c40f
Add a glossary (#8166)
iHiD Sep 10, 2025
93c81f6
Add language selector modal (#8152)
dem4ron Sep 10, 2025
fb4f0fc
Add LocalizationForm (#8154)
dem4ron Sep 10, 2025
38ae462
Describe translations (#8184)
dem4ron Sep 10, 2025
7c963b8
Add before_action to ensure translator_locale is set
iHiD Sep 10, 2025
2b17d0e
Comment out external_language_selector_modal
dem4ron Sep 10, 2025
cbb5f18
Move copy
dem4ron Sep 10, 2025
949c6b1
Update copy
dem4ron Sep 10, 2025
2bbd010
Add missing translations
dem4ron Sep 10, 2025
fcaf583
Adjust scripts
dem4ron Sep 10, 2025
9fd854a
Adjust pages_controller
dem4ron Sep 10, 2025
336617e
Fix JS tests
dem4ron Sep 10, 2025
24e6e14
Reset some bits to push now. (#8201)
iHiD Sep 10, 2025
1350135
Fix localization glossary entry proposal factory
iHiD Sep 10, 2025
8af353c
Align serializer tests with updated serializer output
iHiD Sep 10, 2025
2915acb
Fix incorrect route helper usage in student cancels mentor request test
iHiD Sep 10, 2025
a63a85d
Fix test
iHiD Sep 10, 2025
55b38da
Fix i18n notification issues
iHiD Sep 10, 2025
c1b6cca
Fix issue with newlines
iHiD Sep 10, 2025
3da5dcc
Fis the test
iHiD Sep 10, 2025
dff3a83
Adjust permissions (#8205)
dem4ron Sep 10, 2025
b137c3a
Fix activity ticker test to handle i18n text with non-breaking spaces
iHiD Sep 10, 2025
b6e310a
Redirect to `next` after any action
dem4ron Sep 10, 2025
7bdc6e2
Add more glossaries
iHiD Sep 10, 2025
64556d3
Add more glossaries
iHiD Sep 10, 2025
0154054
Add glossary translations for remaining 10 locales
iHiD Sep 10, 2025
dd4597d
Create glossary-entries documentation
dem4ron Sep 11, 2025
4950c8d
Add skip functionality
dem4ron Sep 11, 2025
d5deee1
Adjust checked status type
dem4ron Sep 11, 2025
46eabca
Exclude ids
dem4ron Sep 11, 2025
3c3a7e6
Redirect on 404 next entry
dem4ron Sep 11, 2025
acba146
Tiny tweaks
dem4ron Sep 11, 2025
fd19e66
Tweak redirecting
dem4ron Sep 11, 2025
c2ea2ba
Locales: All
dem4ron Sep 11, 2025
874a835
Adjust user permissions
dem4ron Sep 11, 2025
76c6f0b
Add settings tab, point links to that.
dem4ron Sep 11, 2025
227164b
Improve styling
iHiD Sep 11, 2025
86c2144
Focus text on edit
dem4ron Sep 11, 2025
ee189df
Improve name-for-locale (#8207)
dem4ron Sep 11, 2025
e9dea50
DRY component, fix spelling, use emojis sparingly (#8208)
dem4ron Sep 11, 2025
54b75e6
Refactor modal, tweak searching (#8209)
dem4ron Sep 11, 2025
3017de0
id -> uuid
dem4ron Sep 11, 2025
f1ce38b
Add excluded_ids parameter to Localization::GlossaryEntry::Search
iHiD Sep 11, 2025
996d6c0
Extend Localization::GlossaryEntry::Search to include proposals
iHiD Sep 11, 2025
b621fa2
Randomize order of search results using RAND()
iHiD Sep 11, 2025
5898783
Change excluded_ids to exclude_uuids using UUIDs
iHiD Sep 11, 2025
5c4c7f9
Tweak green
dem4ron Sep 11, 2025
e0d16d8
Comment out language switching
iHiD Sep 11, 2025
3c0d06b
Fix glossary import
iHiD Sep 11, 2025
7927407
Disable button for 2 secondson mount
dem4ron Sep 11, 2025
32e7182
Add statuses
iHiD Sep 11, 2025
c2cb940
Fix whitespace
iHiD Sep 11, 2025
9535757
Status tag tweaks, css tweaks, disable "Propose new term" (#8210)
dem4ron Sep 11, 2025
8cd2430
`show` tweaks (#8212)
dem4ron Sep 12, 2025
9b2ce70
Add tests (#8213)
dem4ron Sep 12, 2025
50f5f34
Add toasts (#8214)
dem4ron Sep 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 3 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,11 @@ jobs:
bundle exec setup_exercism_local_aws

- name: Install Chrome
env:
CHROME_VERSION: "127.0.6533.119"
run: |
# Download specific Chrome version
wget https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}-1_amd64.deb
# Download current stable Chrome version
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
# Install Chrome
sudo apt-get install -y --allow-downgrades ./google-chrome-stable_${CHROME_VERSION}-1_amd64.deb
sudo apt-get install -y ./google-chrome-stable_current_amd64.deb

- uses: nanasess/setup-chromedriver@v2
- name: Start chromedriver
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@ config/settings.local.yml
app/javascript/i18n/.env
vendor/bundle/

# Claude local settings
# Claude files
.claude/
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ AllCops:
- "node_modules/**/*"
- "lib/solargraph-rails.rb"
- "scripts/**/*"
- "i18n/**/*"
- "config/initializers/i18n.rb"

Bundler/OrderedGems:
Enabled: false
Expand Down
12 changes: 12 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ rm -rf .built-assets/ # Clear asset cache if needed
- `app/controllers/spi/` - Internal service endpoints
- `test/` - All test files
- `docs/context/` - Detailed component documentation
- `scripts/llm/` - Helper scripts created by AI agents

**Script Organization:**

- **Always create new scripts in `scripts/llm/`** - This keeps AI-generated scripts organized and separate from core application scripts
- Use descriptive names like `merge_locales.rb` or `fix_data.rb`
- Include comments explaining what the script does and when to use it

**When editing:**

Expand All @@ -109,6 +116,11 @@ rm -rf .built-assets/ # Clear asset cache if needed
4. Add appropriate tests (see testing docs)
5. Run pre-commit validation commands

**Code Style:**

- **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)
- This applies to serializers, API responses, and all Ruby hash structures

## Reference Documentation

- `docs/context/overview.md` - Complete codebase documentation and setup guide
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ gem 'octokit' # GitHub
gem 'mandate', '~> 2.0'
gem 'kaminari'
gem 'oj', '~> 3.14.0'
gem 'i18n', '>= 0.5.0'

# Setup dependencies
gem 'exercism-config', '>= 0.130.0'
Expand Down
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ DEPENDENCIES
haml_lint
hamlit
humanize
i18n (>= 0.5.0)
image_processing (~> 1.2)
jsbundling-rails
kaminari
Expand Down
32 changes: 32 additions & 0 deletions app/assemblers/assemble_localization_glossary_entries.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class AssembleLocalizationGlossaryEntries
include Mandate

initialize_with :user, :params

def self.keys
%i[criteria status page exclude_uuids]
end

def call
SerializePaginatedCollection.(
glossary_entries,
serializer: SerializeLocalizationGlossaryEntries,
serializer_args: user,
meta: {
unscoped_total: Localization::GlossaryEntry.count
}
)
end

memoize
def glossary_entries
Localization::GlossaryEntry::Search.(
user,
criteria: params[:criteria],
status: params[:status],
page: params[:page],
locale: params[:filter_locale],
exclude_uuids: params[:exclude_uuids]
)
end
end
30 changes: 30 additions & 0 deletions app/assemblers/assemble_localization_originals.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class AssembleLocalizationOriginals
include Mandate

initialize_with :user, :params

def self.keys
%i[criteria status page]
end

def call
SerializePaginatedCollection.(
translations,
serializer: SerializeLocalizationOriginals,
serializer_args: user,
meta: {
unscoped_total: Localization::Original.count
}
)
end

memoize
def translations
Localization::Original::Search.(
user,
criteria: params[:criteria],
status: params[:status],
page: params[:page]
)
end
end
22 changes: 22 additions & 0 deletions app/channels/localization_translation_channel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class LocalizationTranslationChannel < ApplicationCable::Channel
def self.broadcast!(translation)
ActionCable.server.broadcast(
channel_name(translation.locale),
{
key: translation.key,
locale: translation.locale,
value: translation.value
}
)
end

def subscribed
stream_from self.class.channel_name(params[:locale])
end

def unsubscribed; end

def self.channel_name(locale)
"localization_translations_#{locale}"
end
end
3 changes: 2 additions & 1 deletion app/commands/cache/key_for_footer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ def call
parts << ::Track.num_active
parts << user_part
parts << stripe_version
parts << "v3"
parts << I18n.available_locales.count
parts << "3" # Basic expiry key

parts.join(':')
end
Expand Down
29 changes: 26 additions & 3 deletions app/commands/generic_exercise/create_or_update.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,43 @@
class GenericExercise::CreateOrUpdate
include Mandate

initialize_with :slug, :title, :blurb, :source, :source_url, :deep_dive_youtube_id, :deep_dive_blurb, :status
initialize_with :repo_exercise

def call
create!.tap do |exercise|
exercise.update!(attributes)

localize!(:generic_exercise_instructions, repo_exercise.instructions, exercise)
localize!(:generic_exercise_introduction, repo_exercise.introduction, exercise)
localize!(:generic_exercise_title, repo_exercise.title, exercise)
localize!(:generic_exercise_blurb, repo_exercise.blurb, exercise)
localize!(:generic_exercise_source, repo_exercise.source, exercise)
end
end

private
def create!
GenericExercise.find_create_or_find_by!(slug:) do |exercise|
GenericExercise.find_create_or_find_by!(slug: repo_exercise.slug) do |exercise|
exercise.attributes = attributes
end
end

def attributes = { title:, blurb:, source:, source_url:, deep_dive_youtube_id:, deep_dive_blurb:, status: }
def localize!(type, content, exercise)
return unless content.present?

Localization::Text::AddToLocalization.defer(type, content, exercise)
end

memoize
def attributes
{
title: repo_exercise.title,
blurb: repo_exercise.blurb&.truncate(255),
source: repo_exercise.source,
source_url: repo_exercise.source_url,
deep_dive_youtube_id: repo_exercise.deep_dive_youtube_id,
deep_dive_blurb: repo_exercise.deep_dive_blurb,
status: repo_exercise.deprecated? ? :deprecated : :active
}
end
end
10 changes: 10 additions & 0 deletions app/commands/git/sync_blog.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,20 @@ def create_or_update_post(data)
end

post.update!(attributes)

localize!(:post_title, post.title, post.id)
localize!(:post_description, post.description, post.id)
localize!(:post_content, post.content, post.id)
rescue StandardError => e
Github::Issue::OpenForBlogSyncFailure.(e, repo.head_commit.oid)
end

def localize!(type, content, post_id)
return unless content.present?

Localization::Text::AddToLocalization.defer(type, content, post_id)
end

def create_or_update_story(data)
interviewer = User.find_by!(handle: data[:interviewer_handle])
interviewee = User.find_by!(handle: data[:interviewee_handle])
Expand Down
11 changes: 11 additions & 0 deletions app/commands/git/sync_concept.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,22 @@ def call

Git::SyncConceptAuthors.(concept)
Git::SyncConceptContributors.(concept)

localize!(:concept_name, concept.name)
localize!(:concept_blurb, concept.blurb)
localize!(:concept_introduction, concept.introduction)
localize!(:concept_about, concept.about)
end

private
attr_reader :concept, :force_sync

def localize!(type, content)
return unless content.present?

Localization::Text::AddToLocalization.defer(type, content, concept.id)
end

def concept_needs_updating?
track_config_concept_modified? || concept_config_modified?
end
Expand Down
12 changes: 12 additions & 0 deletions app/commands/git/sync_concept_exercise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,23 @@ def call
Git::SyncExerciseArticles.(exercise)
::Exercise::UpdateHasApproaches.(exercise)
SiteUpdates::ProcessNewExerciseUpdate.(exercise)

localize!(:exercise_instructions, exercise.instructions, exercise)
localize!(:exercise_introduction, exercise.introduction, exercise)
localize!(:exercise_title, exercise.title, exercise)
localize!(:exercise_blurb, exercise.blurb, exercise)
localize!(:exercise_source, exercise.source, exercise)
end

private
attr_reader :exercise, :force_sync

def localize!(type, content, exercise)
return unless content.present?

Localization::Text::AddToLocalization.defer(type, content, exercise)
end

def exercise_needs_updating?
track_config_exercise_modified? || exercise_config_modified? || exercise_files_modified?
end
Expand Down
12 changes: 12 additions & 0 deletions app/commands/git/sync_practice_exercise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,23 @@ def call
Git::SyncExerciseArticles.(exercise)
::Exercise::UpdateHasApproaches.(exercise)
SiteUpdates::ProcessNewExerciseUpdate.(exercise)

localize!(:exercise_instructions, exercise.instructions, exercise)
localize!(:exercise_introduction, exercise.introduction, exercise)
localize!(:exercise_title, exercise.title, exercise)
localize!(:exercise_blurb, exercise.blurb, exercise)
localize!(:exercise_source, exercise.source, exercise)
end

private
attr_reader :exercise, :force_sync

def localize!(type, content, exercise)
return unless content.present?

Localization::Text::AddToLocalization.defer(type, content, exercise)
end

def exercise_needs_updating?
track_config_exercise_modified? || exercise_config_modified? || exercise_files_modified?
end
Expand Down
7 changes: 1 addition & 6 deletions app/commands/git/sync_problem_specifications.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@ def call
repo.update!

repo.exercises.each do |exercise|
GenericExercise::CreateOrUpdate.(
exercise.slug, exercise.title, exercise.blurb,
exercise.source, exercise.source_url,
exercise.deep_dive_youtube_id, exercise.deep_dive_blurb,
exercise.deprecated? ? :deprecated : :active
)
GenericExercise::CreateOrUpdate.(exercise)
rescue StandardError => e
Bugsnag.notify(e)
end
Expand Down
22 changes: 22 additions & 0 deletions app/commands/llm/exec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class LLM::Exec
include Mandate

initialize_with :service, :model, :prompt, :spi_endpoint, :stream_channel

def call
raise "Service cannot be nil" if service.nil?
raise "Model cannot be nil" if model.nil?
raise "Prompt cannot be nil" if prompt.nil?
raise "SPI endpoint cannot be nil" if spi_endpoint.nil?

LLM::ExecWithPayload.(
{
service: service,
model: model,
spi_endpoint: "llm/#{spi_endpoint}",
stream_channel: stream_channel,
prompt: prompt
}
)
end
end
9 changes: 9 additions & 0 deletions app/commands/llm/exec_gemini_flash.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class LLM::ExecGeminiFlash
include Mandate

initialize_with :prompt, :spi_endpoint, stream_channel: nil

def call
LLM::Exec.(:gemini, :flash, prompt, spi_endpoint, stream_channel)
end
end
18 changes: 18 additions & 0 deletions app/commands/llm/exec_with_payload.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Don't call this directly unless you're
# dealing with specific LLM retries etc.
# Instead use the LLM::Exec command.
class LLM::ExecWithPayload
include Mandate

initialize_with :payload

def call
RestClient.post(
proxy_url,
payload.to_json,
{ content_type: :json, accept: :json }
)
end

def proxy_url = "http://localhost:8080/exec"
end
11 changes: 11 additions & 0 deletions app/commands/localization/cache/retrieve.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Localization::Cache::Retrieve
include Mandate

initialize_with :locale, :key

def call
return unless Localization.use_cache

Localization.translations.dig(locale.to_sym, key)
end
end
Loading
Loading