diff --git a/.ruby-version b/.ruby-version index f13c6f4..fa7adc7 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-3.3.5 +3.3.5 diff --git a/app/models/concerns/translatable_name.rb b/app/models/concerns/translatable_name.rb new file mode 100644 index 0000000..800c9cc --- /dev/null +++ b/app/models/concerns/translatable_name.rb @@ -0,0 +1,22 @@ +# app/models/concerns/translatable_name.rb +module TranslatableName + extend ActiveSupport::Concern + + def localized_name(default_locale = "en") + return name unless translations.present? + + translations_hash = translations.is_a?(String) ? JSON.parse(translations) : translations + + # 尝试完全匹配当前语言设置 + current_locale = I18n.locale.to_s + return translations_hash[current_locale] if translations_hash[current_locale].present? + + # 尝试匹配语言的基础部分(例如 'zh-CN' => 'zh') + base_locale = current_locale.split("-").first + matching_key = translations_hash.keys.find { |k| k.start_with?(base_locale) } + return translations_hash[matching_key] if matching_key.present? + + # 如果没有匹配,返回默认语言的翻译或原始名称 + translations_hash[default_locale] || name + end +end diff --git a/app/models/country.rb b/app/models/country.rb index d9000dd..2647ddc 100644 --- a/app/models/country.rb +++ b/app/models/country.rb @@ -1,8 +1,9 @@ class Country < ApplicationRecord + include TranslatableName extend FriendlyId friendly_id :name, use: :slugged - before_save :format_timezones + # before_save :format_json_attributes, :timezones, :translations belongs_to :region, optional: true belongs_to :subregion, optional: true @@ -13,14 +14,15 @@ class Country < ApplicationRecord validates :code, presence: true, uniqueness: true validates :iso2, uniqueness: true, allow_blank: true + serialize :translations, coder: JSON def to_s name end - def localized_name - I18n.t("countries.#{code}") - end + # def localized_name + # I18n.t("countries.#{code}") + # end def self.ransackable_attributes(auth_object = nil) [ "code", "created_at", "id", "id_value", "name", "region_id", "slug", "updated_at" ] @@ -32,26 +34,26 @@ class Country < ApplicationRecord private - def format_timezones - return unless timezones.is_a?(String) - - # 使用正则替换 => 为 : - json_string = timezones.gsub(/=>/, ":") - - # 清理多余的空格 - json_string = json_string.gsub(/\s+/, " ").strip - - begin - # 验证是否为有效的 JSON - parsed_json = JSON.parse(json_string) - self.timezones = parsed_json.to_json - rescue JSON::ParserError - # 如果转换失败,可以选择: - # 1. 保持原值 - # 2. 设置为空数组 - # 3. 记录错误日志 - Rails.logger.error("Invalid JSON format for country #{id}: #{timezones}") - self.timezones = "[]" - end - end + # def format_timezones + # return unless timezones.is_a?(String) + # + # # 使用正则替换 => 为 : + # json_string = timezones.gsub(/=>/, ":") + # + # # 清理多余的空格 + # json_string = json_string.gsub(/\s+/, " ").strip + # + # begin + # # 验证是否为有效的 JSON + # parsed_json = JSON.parse(json_string) + # self.timezones = parsed_json.to_json + # rescue JSON::ParserError + # # 如果转换失败,可以选择: + # # 1. 保持原值 + # # 2. 设置为空数组 + # # 3. 记录错误日志 + # Rails.logger.error("Invalid JSON format for country #{id}: #{timezones}") + # self.timezones = "[]" + # end + # end end diff --git a/app/models/region.rb b/app/models/region.rb index b5461e0..aea379c 100644 --- a/app/models/region.rb +++ b/app/models/region.rb @@ -1,4 +1,6 @@ class Region < ApplicationRecord + include TranslatableName + extend FriendlyId friendly_id :name, use: :slugged @@ -9,13 +11,15 @@ class Region < ApplicationRecord validates :name, presence: true, uniqueness: true validates :code, presence: true, uniqueness: true + serialize :translations, coder: JSON + def to_s name end - def localized_name - I18n.t("regions.#{code}") - end + # def localized_name + # I18n.t("regions.#{code}") + # end # 模型中允许被搜索的关联 def self.ransackable_associations(auth_object = nil) diff --git a/app/views/arts/index.html.erb b/app/views/arts/index.html.erb index cbead18..67d34c3 100644 --- a/app/views/arts/index.html.erb +++ b/app/views/arts/index.html.erb @@ -26,7 +26,7 @@ <% if featured_art %>
- Latest from <%= featured_art.city.name %>, <%= featured_art.city.country.name %> + <%= "#{t("text.latest_from")} #{featured_art.city.full_name}" %> <%= featured_art.formatted_time(:date) %>
@@ -47,18 +47,18 @@ - <%= params[:sort] == 'oldest' ? 'Oldest First' : 'Newest First' %> + <%= params[:sort] == 'oldest' ? t("text.oldest_first") : t("text.newest_first") %> @@ -70,7 +70,7 @@ - <%= @current_region&.name || t("text.all_regions") %> + <%= @current_region&.localized_name || t("text.all_regions") %> @@ -83,7 +83,7 @@
<% @regions.each do |region| %>
  • - <%= link_to region.name, arts_path(region: region.id, sort: params[:sort]), + <%= link_to region.localized_name, arts_path(region: region.id, sort: params[:sort]), class: "#{'active' if @current_region == region}" %>
  • <% end %> @@ -95,7 +95,7 @@
    <%= "#{t("text.showing")} #{@weather_arts.total_count} #{t("text.weather_arts")} " %> <% if @current_region %> - from <%= @current_region.name %> + from <%= @current_region.localized_name %> <% end %>
    @@ -118,7 +118,7 @@ <%= art.city.name %>

    - <%= art.city.country.name %> + <%= "#{art.city&.country&.emoji + " " || ""}#{art.city&.country&.localized_name}" %>

    diff --git a/app/views/cities/index.html.erb b/app/views/cities/index.html.erb index 1927c92..6db8f84 100644 --- a/app/views/cities/index.html.erb +++ b/app/views/cities/index.html.erb @@ -27,7 +27,7 @@
    <%= t("text.latest_from") %> <%= featured_art.city.name %>, - <%= featured_art.city.country.name %> + <%= featured_art.city.country.localized_name %> <%= featured_art.formatted_time(:date) %>
    @@ -50,7 +50,7 @@ - <%= @current_region&.name || t("text.all_regions") %> + <%= @current_region&.localized_name || t("text.all_regions") %> @@ -65,7 +65,7 @@
    <% @regions.each do |region| %>
  • - <%= link_to region.name, + <%= link_to region.localized_name, cities_path(region: region.slug), class: "#{@current_region == region ? 'active' : ''}" %>
  • @@ -79,21 +79,21 @@ - <%= @current_country&.name || "All Countries" %> + <%= @current_country&.localized_name || t("text.all_countries") %>
    diff --git a/app/views/layouts/_navbar.html.erb b/app/views/layouts/_navbar.html.erb index fcb64ac..ffa87ec 100644 --- a/app/views/layouts/_navbar.html.erb +++ b/app/views/layouts/_navbar.html.erb @@ -35,9 +35,10 @@ <%= t("title.sign_in") %> - <%= render 'shared/language_switcher' %> <% end %> <% end %> + + <%= render 'shared/language_switcher' %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index b0b2841..4f90c04 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -14,8 +14,10 @@ url: request.original_url }, alternate: { + "en" => url_for(locale: 'en'), "zh-CN" => url_for(locale: 'zh-CN'), - "en" => url_for(locale: 'en') + "ja" => url_for(locale: 'ja'), + "ko" => url_for(locale: 'ko') } ) %> <%= csrf_meta_tags %> diff --git a/app/views/shared/_language_switcher.html.erb b/app/views/shared/_language_switcher.html.erb index 373e85b..2069c4b 100644 --- a/app/views/shared/_language_switcher.html.erb +++ b/app/views/shared/_language_switcher.html.erb @@ -1,17 +1,26 @@ +<%# app/views/shared/_language_switcher.html.erb %> \ No newline at end of file diff --git a/app/views/weather_arts/_weather_stats.html.erb b/app/views/weather_arts/_weather_stats.html.erb index df082f5..b4e6a1d 100644 --- a/app/views/weather_arts/_weather_stats.html.erb +++ b/app/views/weather_arts/_weather_stats.html.erb @@ -1,37 +1,37 @@ <%# Partial _weather_stats.html.erb %>
    -
    Temperature
    +
    <%= t("card.temperature") %>
    <%= weather_art.temperature %>°C
    -
    Feels like <%= weather_art.feeling_temp %>°C
    +
    <%= t("card.feel_like") %> <%= weather_art.feeling_temp %>°C
    -
    Wind
    +
    <%= t("card.wind") %>
    <%= weather_art.wind_scale %>
    <%= weather_art.wind_speed %> km/h
    -
    Humidity
    +
    <%= t("card.humidity") %>
    <%= weather_art.humidity %>%
    -
    Relative humidity
    +
    <%= t("card.relative_humidity") %>
    -
    Visibility
    +
    <%= t("card.visibility") %>
    <%= weather_art.visibility %> km
    -
    Clear view distance
    +
    <%= t("card.clear_view_distance") %>
    -
    Pressure
    +
    <%= t("card.pressure") %>
    <%= weather_art.pressure %> hPa
    -
    Atmospheric pressure
    +
    <%= t("card.atmospheric_pressure") %>
    -
    Cloud Cover
    +
    <%= t("card.cloud_cover") %>
    <%= weather_art.cloud %>%
    -
    Sky coverage
    +
    <%= t("card.sky_coverage") %>
    \ No newline at end of file diff --git a/app/views/weather_arts/show.html.erb b/app/views/weather_arts/show.html.erb index 77740cb..f217800 100644 --- a/app/views/weather_arts/show.html.erb +++ b/app/views/weather_arts/show.html.erb @@ -23,7 +23,7 @@
    - <%= "#{@weather_art&.city&.country&.emoji + " " || ""}#{@city&.country&.name}" %> + <%= "#{@weather_art&.city&.country&.emoji + " " || ""}#{@city&.country&.localized_name}" %>
    <%= @weather_art&.city&.state&.name %> @@ -82,7 +82,7 @@ -

    AI Prompt

    +

    <%= t("title.ai_prompt") %>

    <%= @weather_art.prompt %> diff --git a/config/initializers/locale.rb b/config/initializers/locale.rb index 37d0f4b..5138b08 100644 --- a/config/initializers/locale.rb +++ b/config/initializers/locale.rb @@ -5,7 +5,7 @@ require "i18n/backend/fallbacks" I18n.load_path += Dir[Rails.root.join("config", "locales", "*.{rb,yml}")] # Permitted locales available for the application -I18n.available_locales = [ :en, :"zh-CN" ] +I18n.available_locales = [ :en, :"zh-CN", :ja, :ko ] I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) # I18n::Backend::Simple.include I18n::Backend::Fallbacks diff --git a/config/locales/countries.en.yml b/config/locales/countries.en.yml deleted file mode 100644 index 7c0b858..0000000 --- a/config/locales/countries.en.yml +++ /dev/null @@ -1,55 +0,0 @@ -en: - countries: - # East Asia - CN: 'China' - JP: 'Japan' - KR: 'South Korea' - TW: 'Taiwan' - HK: 'Hong Kong' - - # South Asia - IN: 'India' - PK: 'Pakistan' - BD: 'Bangladesh' - - # Southeast Asia - ID: 'Indonesia' - VN: 'Vietnam' - TH: 'Thailand' - MM: 'Myanmar' - SG: 'Singapore' - - # Middle East - TR: 'Turkey' - IR: 'Iran' - SA: 'Saudi Arabia' - IQ: 'Iraq' - - # Africa - NG: 'Nigeria' - EG: 'Egypt' - CD: 'Democratic Republic of the Congo' - TZ: 'Tanzania' - ZA: 'South Africa' - KE: 'Kenya' - AO: 'Angola' - ML: 'Mali' - CI: 'Ivory Coast' - - # Europe - RU: 'Russia' - GB: 'United Kingdom' - DE: 'Germany' - - # North America - US: 'United States' - MX: 'Mexico' - - # South America - BR: 'Brazil' - PE: 'Peru' - CO: 'Colombia' - CL: 'Chile' - - # Oceania - AU: 'Australia' \ No newline at end of file diff --git a/config/locales/countries.zh-CN.yml b/config/locales/countries.zh-CN.yml deleted file mode 100644 index 0547529..0000000 --- a/config/locales/countries.zh-CN.yml +++ /dev/null @@ -1,55 +0,0 @@ -zh-CN: - countries: - # East Asia - CN: '中国' - JP: '日本' - KR: '韩国' - TW: '台湾' - HK: '香港' - - # South Asia - IN: '印度' - PK: '巴基斯坦' - BD: '孟加拉国' - - # Southeast Asia - ID: '印度尼西亚' - VN: '越南' - TH: '泰国' - MM: '缅甸' - SG: '新加坡' - - # Middle East - TR: '土耳其' - IR: '伊朗' - SA: '沙特阿拉伯' - IQ: '伊拉克' - - # Africa - NG: '尼日利亚' - EG: '埃及' - CD: '刚果民主共和国' - TZ: '坦桑尼亚' - ZA: '南非' - KE: '肯尼亚' - AO: '安哥拉' - ML: '马里' - CI: '科特迪瓦' - - # Europe - RU: '俄罗斯' - GB: '英国' - DE: '德国' - - # North America - US: '美国' - MX: '墨西哥' - - # South America - BR: '巴西' - PE: '秘鲁' - CO: '哥伦比亚' - CL: '智利' - - # Oceania - AU: '澳大利亚' \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index f73d8f2..a026f71 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -28,6 +28,11 @@ # enabled: "ON" en: + language: + en: "English" + zh-CN: "简体中文" + ja: "日本語" + ko: "한국어" hello: "Hello world" brand: name: "Today AI Weather" @@ -40,12 +45,17 @@ en: admin_dashboard: "Admin Dashboard" latest_weather_art: "Latest Weather Art" popular_weather_art: "Popular Weather Art" + ai_prompt: "AI Prompt" text: latest_from: "Latest from" search_cities: "Search cities..." all_regions: "All Regions" + all_countries: "All Countries" + all_in: "All in" showing: "Showing" weather_arts: "Weather Arts" + newest_first: "Newest First" + oldest_first: "Oldest First" cities: title: "Explore Cities" arts: @@ -62,6 +72,18 @@ en: view_all_weather_arts: "View All Weather Arts" back_to_cities: "Back to Cities" back_to: "Back to" + card: + temperature: "Temperature" + wind: "Wind" + humidity: "Humidity" + visibility: "Visibility" + pressure: "Pressure" + cloud_cover: "Cloud Cover" + feel_like: "Feels like" + relative_humidity: "Relative humidity" + clear_view_distance: "Clear view distance" + atmospheric_pressure: "Atmospheric pressure" + sky_coverage: "Sky coverage" pagination: showing_items: "Showing %{from} to %{to} of %{total} %{items}" items: diff --git a/config/locales/ja.yml b/config/locales/ja.yml new file mode 100644 index 0000000..6677335 --- /dev/null +++ b/config/locales/ja.yml @@ -0,0 +1,62 @@ +ja: + hello: "こんにちは世界" + brand: + name: "今日のAI天気" + title: + cities: "都市" + arts: "アート" + sign_in: "サインイン" + sign_out: "サインアウト" + settings: "設定" + admin_dashboard: "管理者ダッシュボード" + latest_weather_art: "最新の天気アート" + popular_weather_art: "人気の天気アート" + ai_prompt: "AIプロンプト" + text: + latest_from: "最新情報" + search_cities: "都市を検索..." + all_regions: "すべての地域" + all_countries: "すべての国" + all_in: "すべて含む" + showing: "表示中" + weather_arts: "天気アート" + newest_first: "最新順" + oldest_first: "古い順" + cities: + title: "都市を探る" + arts: + title: "天気アートギャラリー" + subtitle: "世界中の都市から生成されたAI天気アートを発見" + home: + headline_html: 天気が出会う場所
    人工知能 + subtitle: + AI生成アートのレンズを通して天気を体験し、 + 日常の気象現象に新しい視点をもたらします。 + button: + explore_cities: "都市を探る" + view_detail: "詳細を見る" + view_all_weather_arts: "すべての天気アートを見る" + back_to_cities: "都市に戻る" + back_to: "戻る" + card: + temperature: "温度" + wind: "風" + humidity: "湿度" + visibility: "視界" + pressure: "圧力" + cloud_cover: "雲の覆い" + feel_like: "体感温度" + relative_humidity: "相対湿度" + clear_view_distance: "クリアビュー距離" + atmospheric_pressure: "大気圧" + sky_coverage: "空の覆い" + pagination: + showing_items: "合計 %{total} %{items} のうち %{from} から %{to} まで表示" + items: + weather: "天気記録" + default: "アイテム" + time: + formats: + time_only: "%H:%M" + with_zone: "%{time} %{zone}" + date_and_time: "%{date} %{time}" \ No newline at end of file diff --git a/config/locales/ko.yml b/config/locales/ko.yml new file mode 100644 index 0000000..7f323c3 --- /dev/null +++ b/config/locales/ko.yml @@ -0,0 +1,62 @@ +ko: + hello: "안녕하세요 세계" + brand: + name: "오늘의 AI 날씨" + title: + cities: "도시" + arts: "예술" + sign_in: "로그인" + sign_out: "로그아웃" + settings: "설정" + admin_dashboard: "관리자 대시보드" + latest_weather_art: "최신 날씨 예술" + popular_weather_art: "인기 있는 날씨 예술" + ai_prompt: "AI 프롬프트" + text: + latest_from: "최신 소식" + search_cities: "도시 검색..." + all_regions: "모든 지역" + all_countries: "모든 국가" + all_in: "모두 포함" + showing: "표시 중" + weather_arts: "날씨 예술" + newest_first: "최신순" + oldest_first: "오래된 순" + cities: + title: "도시 탐험" + arts: + title: "날씨 예술 갤러리" + subtitle: "전 세계 도시에서 생성된 AI 날씨 예술 발견하기" + home: + headline_html: 날씨가 만나는 곳
    인공지능 + subtitle: + AI 생성 예술의 렌즈를 통해 날씨를 경험하세요, + 일상적인 기상 현상에 대한 새로운 관점을 제공합니다. + button: + explore_cities: "도시 탐험" + view_detail: "상세 보기" + view_all_weather_arts: "모든 날씨 예술 보기" + back_to_cities: "도시로 돌아가기" + back_to: "돌아가기" + card: + temperature: "온도" + wind: "바람" + humidity: "습도" + visibility: "가시성" + pressure: "압력" + cloud_cover: "구름 덮개" + feel_like: "체감 온도" + relative_humidity: "상대 습도" + clear_view_distance: "맑은 시야 거리" + atmospheric_pressure: "대기압" + sky_coverage: "하늘 덮개" + pagination: + showing_items: "총 %{total} %{items} 중 %{from}에서 %{to}까지 표시" + items: + weather: "날씨 기록" + default: "항목" + time: + formats: + time_only: "%H:%M" + with_zone: "%{time} %{zone}" + date_and_time: "%{date} %{time}" \ No newline at end of file diff --git a/config/locales/regions.en.yml b/config/locales/regions.en.yml deleted file mode 100644 index 8558328..0000000 --- a/config/locales/regions.en.yml +++ /dev/null @@ -1,15 +0,0 @@ -en: - regions: - AS: 'Asia' - SA: 'South Asia' - SEA: 'Southeast Asia' - EA: 'East Asia' - ME: 'Middle East' - AF: 'Africa' - NA: 'North Africa' - SSA: 'Sub-Saharan Africa' - EU: 'Europe' - NAM: 'North America' - SAM: 'South America' - CAM: 'Central America' - OC: 'Oceania' \ No newline at end of file diff --git a/config/locales/regions.zh-CN.yml b/config/locales/regions.zh-CN.yml deleted file mode 100644 index 14f19f9..0000000 --- a/config/locales/regions.zh-CN.yml +++ /dev/null @@ -1,15 +0,0 @@ -zh-CN: - regions: - AS: '亚洲' - SA: '南亚' - SEA: '东南亚' - EA: '东亚' - ME: '中东' - AF: '非洲' - NA: '北非' - SSA: '撒哈拉以南非洲' - EU: '欧洲' - NAM: '北美洲' - SAM: '南美洲' - CAM: '中美洲' - OC: '大洋洲' \ No newline at end of file diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 82cb45e..5dca8f9 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -11,12 +11,17 @@ zh-CN: admin_dashboard: "管理中枢" latest_weather_art: "气象绘卷·新作" popular_weather_art: "气象绘卷·佳作" + ai_prompt: "天气描述" text: latest_from: "新至之城" search_cities: "寻城觅境…" all_regions: "寰宇之境" + all_countries: "所有国家" + all_in: "全部" showing: "映现" weather_arts: "气象艺境" + newest_first: "最新优先" + oldest_first: "最早优先" cities: title: "云游四海" arts: @@ -32,6 +37,18 @@ zh-CN: view_all_weather_arts: "尽览天工" back_to_cities: "继续探索城市" back_to: "回到" + card: + temperature: "温度" + wind: "风力" + humidity: "湿度" + visibility: "能见度" + pressure: "气压" + cloud_cover: "云量" + feel_like: "体感温度" + relative_humidity: "相对湿度" + clear_view_distance: "清晰视距" + atmospheric_pressure: "大气压力" + sky_coverage: "天空覆盖" pagination: showing_items: "显示第 %{from} 到第 %{to} 条,共 %{total} 条%{items}" items: diff --git a/lib/tasks/sync_geo_data.rake b/lib/tasks/sync_geo_data.rake index 6237b66..47c4b64 100644 --- a/lib/tasks/sync_geo_data.rake +++ b/lib/tasks/sync_geo_data.rake @@ -32,7 +32,7 @@ namespace :geo do region.update!( name: data["name"], code: data["name"], - translations: data["translations"], + translations: data["translations"].to_json, flag: data["flag"] || true, wiki_data_id: data["wikiDataId"] ) @@ -55,7 +55,7 @@ namespace :geo do count += 1 subregion.update!( - translations: data["translations"], + translations: data["translations"].to_json, flag: data["flag"] || true, wiki_data_id: data["wikiDataId"] ) @@ -106,8 +106,8 @@ namespace :geo do tld: data["tld"], native: data["native"], nationality: data["nationality"], - timezones: data["timezones"], - translations: data["translations"], + timezones: data["timezones"].to_json, + translations: data["translations"].to_json, latitude: data["latitude"], longitude: data["longitude"], emoji: data["emoji"],