Skip to content

Commit 8dca7c7

Browse files
iHiDdem4ron
andauthored
Add locales (#8107)
* Add basic translations code/logic * WIP * Update migrations * Update migrations * Add i18n js backend file loader (#8099) * Start work on things * Add approval of LLM version * Improve UI * Add more endpoints * Return data from API * Fix * Improve searching * Add LLM verification * Tweaks * Add localization generating flow * Reword lots * Add locales as URL params * Fix default vs constraint * Fix Zeitwerk * Redirect logged-in users to correct locale * Check routing * Redirect users to their selected locale * Correctly render things * Fix rebase * Add locales to footer * Tidy * Improve locale support * Improve locale support * WIP * Rename migration * Further progress * Add test * Add test * Add tests * Rework prompts * Sync things up * Add Translation Placeholder (#8149) A placeholder that shows a pending state, then shows the newly generated translation. * Add React Translation UI pages * Fix bad filename * Fix LLM verification * Add title to originals * Fix serialzier * Remove stray file --------- Co-authored-by: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Co-authored-by: dem4ron <demaaron88@gmail.com>
1 parent b4a19f9 commit 8dca7c7

165 files changed

Lines changed: 5370 additions & 686 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.

.cursor/rules/general.mdc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
alwaysApply: true
3+
---
4+
Always start by reading .github/copilot-instructions.md and any docs/llm-support that are required.

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ gem 'octokit' # GitHub
3838
gem 'mandate', '~> 2.0'
3939
gem 'kaminari'
4040
gem 'oj', '~> 3.14.0'
41+
gem 'i18n', '>= 0.5.0'
4142

4243
# Setup dependencies
4344
gem 'exercism-config', '>= 0.130.0'

Gemfile.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,7 @@ DEPENDENCIES
665665
haml_lint
666666
hamlit
667667
humanize
668+
i18n (>= 0.5.0)
668669
image_processing (~> 1.2)
669670
jsbundling-rails
670671
kaminari

Procfile.dev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
server: bin/rails server -p 3020
22
anycable: bundle exec anycable
3-
sidekiq: bundle exec sidekiq
3+
#sidekiq: bundle exec sidekiq
44
ws: anycable-go --host='local.exercism.io' --rpc_host='local.exercism.io:50051' --port=3334
55
css: yarn build:css --watch
66
js: yarn build --watch
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
class AssembleLocalizationOriginals
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+
translations,
13+
serializer: SerializeLocalizationOriginals,
14+
serializer_args: user,
15+
meta: {
16+
unscoped_total: Localization::Original.count
17+
}
18+
)
19+
end
20+
21+
memoize
22+
def translations
23+
Localization::Original::Search.(
24+
user,
25+
criteria: params[:criteria],
26+
status: params[:status],
27+
page: params[:page]
28+
)
29+
end
30+
end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class LocalizationTranslationChannel < ApplicationCable::Channel
2+
def self.broadcast!(translation)
3+
ActionCable.server.broadcast(
4+
channel_name(translation.locale),
5+
{
6+
key: translation.key,
7+
locale: translation.locale,
8+
value: translation.value
9+
}
10+
)
11+
end
12+
13+
def subscribed
14+
stream_from self.class.channel_name(params[:locale])
15+
end
16+
17+
def unsubscribed; end
18+
19+
def self.channel_name(locale)
20+
"localization_translations_#{locale}"
21+
end
22+
end

app/commands/cache/key_for_footer.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ def call
99
parts << ::Track.num_active
1010
parts << user_part
1111
parts << stripe_version
12-
parts << "v3"
12+
parts << I18n.available_locales.count
13+
parts << "3" # Basic expiry key
1314

1415
parts.join(':')
1516
end
Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,44 @@
11
class GenericExercise::CreateOrUpdate
22
include Mandate
33

4-
initialize_with :slug, :title, :blurb, :source, :source_url, :deep_dive_youtube_id, :deep_dive_blurb, :status
4+
initialize_with :repo_exercise
55

66
def call
77
create!.tap do |exercise|
88
exercise.update!(attributes)
9+
10+
localize!(:generic_exercise_instructions, exercise.instructions, exercise)
11+
localize!(:generic_exercise_introduction, exercise.introduction, exercise)
12+
localize!(:generic_exercise_title, exercise.title, exercise)
13+
localize!(:generic_exercise_blurb, exercise.blurb, exercise)
14+
localize!(:generic_exercise_source, exercise.source, exercise)
915
end
1016
end
1117

1218
private
1319
def create!
14-
GenericExercise.find_create_or_find_by!(slug:) do |exercise|
20+
GenericExercise.find_create_or_find_by!(slug: repo_exercise[:slug]) do |exercise|
1521
exercise.attributes = attributes
1622
end
1723
end
1824

19-
def attributes = { title:, blurb:, source:, source_url:, deep_dive_youtube_id:, deep_dive_blurb:, status: }
25+
def localize!(type, content, exercise)
26+
return unless content.present?
27+
28+
Localization::Text::AddToLocalization.defer(type, content, exercise)
29+
end
30+
31+
memoize
32+
def attributes
33+
{
34+
slug: repo_exercise[:slug],
35+
title: repo_exercise[:title],
36+
blurb: repo_exercise[:blurb],
37+
source: repo_exercise[:source],
38+
source_url: repo_exercise[:source_url],
39+
deep_dive_youtube_id: repo_exercise[:deep_dive_youtube_id],
40+
deep_dive_blurb: repo_exercise[:deep_dive_blurb],
41+
status: repo_exercise[:deprecated] ? :deprecated : :active
42+
}
43+
end
2044
end

app/commands/git/sync_blog.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,19 @@ def create_or_update_post(data)
3535
end
3636

3737
post.update!(attributes)
38-
rescue StandardError => e
39-
Github::Issue::OpenForBlogSyncFailure.(e, repo.head_commit.oid)
38+
39+
localize!(:post_title, post.title, post.id)
40+
localize!(:post_description, post.description, post.id)
41+
localize!(:post_content, post.content, post.id)
42+
43+
# rescue StandardError => e
44+
# Github::Issue::OpenForBlogSyncFailure.(e, repo.head_commit.oid)
45+
end
46+
47+
def localize!(type, content, post_id)
48+
return unless content.present?
49+
50+
Localization::Text::AddToLocalization.defer(type, content, post_id)
4051
end
4152

4253
def create_or_update_story(data)

app/commands/git/sync_concept.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,22 @@ def call
2020

2121
Git::SyncConceptAuthors.(concept)
2222
Git::SyncConceptContributors.(concept)
23+
24+
localize!(:concept_name, concept.name)
25+
localize!(:concept_blurb, concept.blurb)
26+
localize!(:concept_introduction, concept.introduction)
27+
localize!(:concept_about, concept.about)
2328
end
2429

2530
private
2631
attr_reader :concept, :force_sync
2732

33+
def localize!(type, content)
34+
return unless content.present?
35+
36+
Localization::Text::AddToLocalization.defer(type, content, concept.id)
37+
end
38+
2839
def concept_needs_updating?
2940
track_config_concept_modified? || concept_config_modified?
3041
end

0 commit comments

Comments
 (0)