feat: add internationalization support

- Implement locale extraction and fallback mechanism
- Add translation files for English and Chinese
- Update views to use translated strings for various UI elements

This commit introduces support for multiple languages in the application, enhancing accessibility for users. It includes a fallback mechanism for locales and updates to the user interface to display translated content.
This commit is contained in:
songtianlun 2025-02-21 17:51:25 +08:00
parent 517e3038cc
commit f6b9dcf187
19 changed files with 253 additions and 91 deletions

View File

@ -77,7 +77,17 @@ class ApplicationController < ActionController::Base
private private
def set_locale def set_locale
I18n.locale = params[:locale] || I18n.default_locale I18n.locale = extract_locale || I18n.default_locale
I18n.fallbacks[I18n.locale] = [ I18n.locale, I18n.default_locale ].uniq
end
def extract_locale
parsed_locale = params[:locale]
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
def default_url_options
{ locale: I18n.locale }
end end
def set_content_type_for_rss def set_content_type_for_rss

View File

@ -79,11 +79,23 @@ class WeatherArt < ApplicationRecord
ActiveSupport::TimeZone["UTC"] ActiveSupport::TimeZone["UTC"]
time = self.updated_at time = self.updated_at
date_string = self.weather_date&.strftime("%B %d, %Y") # 使用 I18n 本地化格式化日期
date_string = I18n.l(self.weather_date, format: :long)
# 格式化时间
time_format = use_local_timezone ? time.in_time_zone(time_zone) : time.utc
time_string = time_string =
use_local_timezone ? if use_local_timezone
"#{time.in_time_zone(time_zone).strftime('%H:%M')} #{timezone_info['gmtOffsetName']}" : I18n.t("time.formats.with_zone",
"#{time.utc.strftime('%H:%M')} UTC" time: I18n.l(time_format, format: :time_only),
zone: timezone_info["gmtOffsetName"]
)
else
I18n.t("time.formats.with_zone",
time: I18n.l(time_format, format: :time_only),
zone: "UTC"
)
end
case type case type
when :date when :date
@ -91,9 +103,15 @@ class WeatherArt < ApplicationRecord
when :time when :time
time_string time_string
when :all when :all
"#{date_string} #{time_string}" I18n.t("time.formats.date_and_time",
date: date_string,
time: time_string
)
else else
"#{date_string} #{time_string}" I18n.t("time.formats.date_and_time",
date: date_string,
time: time_string
)
end end
end end

View File

@ -17,10 +17,10 @@
<div class="container mx-auto px-4"> <div class="container mx-auto px-4">
<div class="max-w-3xl mx-auto text-center space-y-6"> <div class="max-w-3xl mx-auto text-center space-y-6">
<h1 class="text-4xl md:text-5xl font-display font-bold"> <h1 class="text-4xl md:text-5xl font-display font-bold">
Weather Arts Gallery <%= t("arts.title") %>
</h1> </h1>
<p class="text-xl text-base-content/70"> <p class="text-xl text-base-content/70">
Discover AI-generated weather art from cities around the world <%= t("arts.subtitle") %>
</p> </p>
<!-- 如果有特色图片,显示其信息 --> <!-- 如果有特色图片,显示其信息 -->
@ -70,14 +70,14 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
<%= @current_region&.name || 'All Regions' %> <%= @current_region&.name || t("text.all_regions") %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg> </svg>
</button> </button>
<ul class="dropdown-content z-[1] menu p-2 shadow-lg bg-base-100 rounded-box w-52"> <ul class="dropdown-content z-[1] menu p-2 shadow-lg bg-base-100 rounded-box w-52">
<li> <li>
<%= link_to "All Regions", arts_path(sort: params[:sort]), <%= link_to t("text.all_regions"), arts_path(sort: params[:sort]),
class: "#{'active' unless @current_region}" %> class: "#{'active' unless @current_region}" %>
</li> </li>
<div class="divider my-1"></div> <div class="divider my-1"></div>
@ -93,7 +93,7 @@
<!-- 结果统计 --> <!-- 结果统计 -->
<div class="text-center text-sm text-base-content/70 mt-4"> <div class="text-center text-sm text-base-content/70 mt-4">
Showing <%= @weather_arts.total_count %> weather arts <%= "#{t("text.showing")} #{@weather_arts.total_count} #{t("text.weather_arts")} " %>
<% if @current_region %> <% if @current_region %>
from <%= @current_region.name %> from <%= @current_region.name %>
<% end %> <% end %>
@ -154,7 +154,7 @@
<%= link_to city_weather_art_path(art.city, art), <%= link_to city_weather_art_path(art.city, art),
class: "btn btn-primary btn-sm w-full" do %> class: "btn btn-primary btn-sm w-full" do %>
View Details <%= t("button.view_detail") %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg> </svg>

View File

@ -75,7 +75,7 @@
<div class="card-actions justify-end"> <div class="card-actions justify-end">
<%= link_to city_path(city), <%= link_to city_path(city),
class: "btn btn-primary btn-sm gap-2", data: { turbo_frame: "_top" } do %> class: "btn btn-primary btn-sm gap-2", data: { turbo_frame: "_top" } do %>
View Details <%= t("button.view_detail") %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg> </svg>
@ -115,7 +115,7 @@
<div class="card-actions justify-end"> <div class="card-actions justify-end">
<%= link_to city_path(city), <%= link_to city_path(city),
class: "btn btn-primary btn-sm gap-2", data: { turbo_frame: "_top" } do %> class: "btn btn-primary btn-sm gap-2", data: { turbo_frame: "_top" } do %>
View Details <%= t("button.view_detail") %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg> </svg>

View File

@ -18,7 +18,7 @@
<%= f.text_field :query, <%= f.text_field :query,
value: params[:query] ? URI.decode_www_form_component(params[:query]) : nil, value: params[:query] ? URI.decode_www_form_component(params[:query]) : nil,
class: "w-full pl-12 pr-12 py-3 rounded-full bg-base-200/80 backdrop-blur border border-base-300 focus:outline-none focus:ring-2 focus:ring-primary/50 transition", class: "w-full pl-12 pr-12 py-3 rounded-full bg-base-200/80 backdrop-blur border border-base-300 focus:outline-none focus:ring-2 focus:ring-primary/50 transition",
placeholder: "Search cities...", placeholder: t("text.search_cities"),
autocomplete: "off", autocomplete: "off",
data: { data: {
action: "input->search#submit", action: "input->search#submit",

View File

@ -16,16 +16,16 @@
<div class="container mx-auto px-4"> <div class="container mx-auto px-4">
<div class="max-w-3xl mx-auto text-center space-y-6"> <div class="max-w-3xl mx-auto text-center space-y-6">
<h1 class="text-5xl md:text-6xl font-display font-bold leading-tight"> <h1 class="text-5xl md:text-6xl font-display font-bold leading-tight">
Explore Cities <%= t("cities.title") %>
</h1> </h1>
<p class="text-xl md:text-2xl text-base-content/70 font-light max-w-2xl mx-auto"> <p class="text-xl md:text-2xl text-base-content/70 font-light max-w-2xl mx-auto">
Discover AI-generated weather art from cities around the world <%= t("arts.subtitle") %>
</p> </p>
<!-- 特色图片信息 --> <!-- 特色图片信息 -->
<% if featured_art %> <% if featured_art %>
<div class="inline-block mt-6 px-4 py-2 bg-base-100/80 backdrop-blur-sm rounded-full text-sm"> <div class="inline-block mt-6 px-4 py-2 bg-base-100/80 backdrop-blur-sm rounded-full text-sm">
Latest from <%= t("text.latest_from") %>
<span class="font-semibold"><%= featured_art.city.name %></span>, <span class="font-semibold"><%= featured_art.city.name %></span>,
<%= featured_art.city.country.name %> <%= featured_art.city.country.name %>
<span class="mx-2">•</span> <span class="mx-2">•</span>
@ -50,7 +50,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
<%= @current_region&.name || 'All Regions' %> <%= @current_region&.name || t("text.all_regions") %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg> </svg>
@ -59,7 +59,7 @@
<li> <li>
<%= link_to cities_path, <%= link_to cities_path,
class: "#{@current_region ? '' : 'active'}" do %> class: "#{@current_region ? '' : 'active'}" do %>
All Regions <%= t("text.all_regions") %>
<% end %> <% end %>
</li> </li>
<div class="divider my-1"></div> <div class="divider my-1"></div>

View File

@ -16,7 +16,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg> </svg>
Back to Cities <%= t("button.back_to_cities") %>
<% end %> <% end %>
</div> </div>

View File

@ -20,7 +20,7 @@
</div> </div>
</div> </div>
<div class="card-actions justify-end mt-4"> <div class="card-actions justify-end mt-4">
<%= link_to "View Details", city_weather_art_path(art.city, art), <%= link_to t("button.view_detail"), city_weather_art_path(art.city, art),
class: "btn btn-primary btn-outline" %> class: "btn btn-primary btn-outline" %>
</div> </div>
</div> </div>

View File

@ -11,13 +11,12 @@
<div class="container mx-auto px-4 h-full flex items-center relative"> <div class="container mx-auto px-4 h-full flex items-center relative">
<div class="max-w-2xl space-y-6"> <div class="max-w-2xl space-y-6">
<h1 class="text-5xl md:text-6xl font-display font-bold leading-tight"> <h1 class="text-5xl md:text-6xl font-display font-bold leading-tight">
Where Weather Meets<br>Artificial Intelligence <%= t("home.headline_html") %>
</h1> </h1>
<p class="text-xl text-base-content/70 font-sans"> <p class="text-xl text-base-content/70 font-sans">
Experience weather through the lens of AI-generated art, <%= t("home.subtitle") %>
bringing a new perspective to daily meteorological phenomena.
</p> </p>
<%= link_to "Explore Cities", cities_path, <%= link_to t("button.explore_cities"), cities_path,
class: "btn btn-primary btn-lg mt-8 font-sans" %> class: "btn btn-primary btn-lg mt-8 font-sans" %>
</div> </div>
</div> </div>
@ -25,9 +24,9 @@
<!-- 最新天气艺术 --> <!-- 最新天气艺术 -->
<section class="container mx-auto px-4 py-16 space-y-12"> <section class="container mx-auto px-4 py-16 space-y-12">
<h2 class="text-3xl font-display font-bold text-center">Latest Weather Art</h2> <h2 class="text-3xl font-display font-bold text-center"><%= t("title.latest_weather_art") %></h2>
<%= render 'home/arts', arts: @latest_arts %> <%= render 'home/arts', arts: @latest_arts %>
<h2 class="text-3xl font-display font-bold text-center">Popular Weather Art</h2> <h2 class="text-3xl font-display font-bold text-center"><%= t("title.popular_weather_art") %></h2>
<%= render 'home/arts', arts: @popular_arts %> <%= render 'home/arts', arts: @popular_arts %>
<!-- <h2 class="text-3xl font-display font-bold text-center">Random Weather Art</h2>--> <!-- <h2 class="text-3xl font-display font-bold text-center">Random Weather Art</h2>-->
<%#= render 'home/arts', arts: @random_arts %> <%#= render 'home/arts', arts: @random_arts %>
@ -35,7 +34,7 @@
</div> </div>
<div class="text-center mt-12 mb-12"> <div class="text-center mt-12 mb-12">
<%= link_to arts_path, class: "btn btn-primary btn-lg gap-2" do %> <%= link_to arts_path, class: "btn btn-primary btn-lg gap-2" do %>
View All Weather Arts <%= t("button.view_all_weather_arts") %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg> </svg>

View File

@ -3,14 +3,14 @@
<!-- Logo --> <!-- Logo -->
<div class="flex-1"> <div class="flex-1">
<%= link_to root_path, class: "text-xl md:text-2xl font-display font-bold hover:text-primary transition-colors" do %> <%= link_to root_path, class: "text-xl md:text-2xl font-display font-bold hover:text-primary transition-colors" do %>
Today AI Weather <%= t('brand.name') %>
<% end %> <% end %>
</div> </div>
<!-- Desktop Menu --> <!-- Desktop Menu -->
<div class="hidden md:flex flex-none gap-2 items-center"> <div class="hidden md:flex flex-none gap-2 items-center">
<%= link_to "Cities", cities_path, class: "btn btn-ghost btn-sm font-sans" %> <%= link_to t("title.cities"), cities_path, class: "btn btn-ghost btn-sm font-sans" %>
<%= link_to "Arts", arts_path, class: "btn btn-ghost btn-sm font-sans" %> <%= link_to t("title.arts"), arts_path, class: "btn btn-ghost btn-sm font-sans" %>
<% if user_signed_in? %> <% if user_signed_in? %>
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">
@ -27,12 +27,15 @@
</label> </label>
<%= render 'layouts/user_menu' %> <%= render 'layouts/user_menu' %>
</div> </div>
<%= render 'shared/language_switcher' %>
<% else %> <% else %>
<%= link_to new_user_session_path, class: "btn btn-primary btn-sm" do %> <%= link_to new_user_session_path, class: "btn btn-primary btn-sm" do %>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" />
</svg> </svg>
<span>Sign in</span> <span><%= t("title.sign_in") %></span>
<%= render 'shared/language_switcher' %>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>
@ -46,8 +49,8 @@
</svg> </svg>
</label> </label>
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52"> <ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
<li><%= link_to "Cities", cities_path %></li> <li><%= link_to t("title.cities"), cities_path %></li>
<li><%= link_to "Arts", arts_path %></li> <li><%= link_to t("title.arts"), arts_path %></li>
<div class="divider my-1"></div> <div class="divider my-1"></div>
<% if user_signed_in? %> <% if user_signed_in? %>
<li> <li>
@ -56,7 +59,7 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg> </svg>
Settings <%= t("title.settings") %>
<% end %> <% end %>
</li> </li>
<li> <li>
@ -67,7 +70,7 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
</svg> </svg>
<span>Sign out</span> <span><% t("title.sign_out") %></span>
</div> </div>
<% end %> <% end %>
</li> </li>
@ -77,7 +80,7 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" />
</svg> </svg>
Sign in <%= t("title.sign_in") %>
<% end %> <% end %>
</li> </li>
<% end %> <% end %>

View File

@ -5,7 +5,7 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg> </svg>
<span>Settings</span> <span><%= t("title.settings") %></span>
<% end %> <% end %>
</li> </li>
@ -15,7 +15,7 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V19.5a2.25 2.25 0 002.25 2.25h.75m0-3H21m-3.75 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V19.5a2.25 2.25 0 002.25 2.25h.75m0-3H21m-3.75 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z" />
</svg> </svg>
<span>Admin Dashboard</span> <span><%= t("title.admin_dashboard") %></span>
<% end %> <% end %>
</li> </li>
<% end %> <% end %>
@ -29,7 +29,7 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
</svg> </svg>
<span>Sign out</span> <span><%= t("title.sign_out") %></span>
<% end %> <% end %>
</li> </li>
</ul> </ul>

View File

@ -0,0 +1,17 @@
<div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-ghost btn-circle">
<% if I18n.locale.to_s == 'en' %>
🇺🇸
<% else %>
🇨🇳
<% end %>
</div>
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
<%= link_to url_for(locale: :en), class: "#{I18n.locale == :en ? 'active' : ''}" do %>
<li><span class="flex items-center gap-2">🇺🇸 English</span></li>
<% end %>
<%= link_to url_for(locale: :'zh-CN'), class: "#{I18n.locale == :'zh-CN' ? 'active' : ''}" do %>
<li><span class="flex items-center gap-2">🇨🇳 简体中文</span></li>
<% end %>
</ul>
</div>

View File

@ -78,9 +78,12 @@
<!-- 结果统计 --> <!-- 结果统计 -->
<div class="text-sm text-base-content/60 font-light"> <div class="text-sm text-base-content/60 font-light">
Showing <%= collection.offset_value + 1 %> to <%= t('pagination.showing_items',
<%= collection.last_page? ? collection.total_count : collection.offset_value + collection.limit_value %> from: collection.offset_value + 1,
of <%= collection.total_count %> <%= collection_name || 'items' %> to: collection.last_page? ? collection.total_count : collection.offset_value + collection.limit_value,
total: collection.total_count,
items: t("pagination.items.#{collection_name}", default: t('pagination.items.default'))
) %>
</div> </div>
</div> </div>
<% end %> <% end %>

View File

@ -35,7 +35,7 @@
<%= link_to city_weather_art_path(weather_art.city, weather_art), <%= link_to city_weather_art_path(weather_art.city, weather_art),
class: "btn btn-primary btn-block" do %> class: "btn btn-primary btn-block" do %>
View Details <%= t("button.view_detail") %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg> </svg>

View File

@ -12,7 +12,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg> </svg>
Back to <%= @weather_art.city.name %> <%= "#{t("button.back_to")} #{@weather_art.city.name}" %>
<% end %> <% end %>
<!-- 标题区域 --> <!-- 标题区域 -->

View File

@ -0,0 +1,19 @@
# config/initializers/locale.rb
require "i18n/backend/fallbacks"
# Where the I18n library should search for translation files
I18n.load_path += Dir[Rails.root.join("config", "locales", "*.{rb,yml}")]
# Permitted locales available for the application
I18n.available_locales = [ :en, :"zh-CN" ]
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
# I18n::Backend::Simple.include I18n::Backend::Fallbacks
# I18n.fallbacks[:en]
I18n.fallbacks = I18n::Locale::Fallbacks.new(
en: [ :en ],
'zh-CN': [ :zh, :zh_cn, :en ]
)
# Set default locale to something other than :en
I18n.default_locale = :en

View File

@ -29,3 +29,46 @@
en: en:
hello: "Hello world" hello: "Hello world"
brand:
name: "Today AI Weather"
title:
cities: "Cities"
arts: "Arts"
sign_in: "Sign in"
sign_out: "Sign out"
settings: "Settings"
admin_dashboard: "Admin Dashboard"
latest_weather_art: "Latest Weather Art"
popular_weather_art: "Popular Weather Art"
text:
latest_from: "Latest from"
search_cities: "Search cities..."
all_regions: "All Regions"
showing: "Showing"
weather_arts: "Weather Arts"
cities:
title: "Explore Cities"
arts:
title: "Weather Arts Gallery"
subtitle: "Discover AI-generated weather art from cities around the world"
home:
headline_html: Where Weather Meets<br>Artificial Intelligence
subtitle:
Experience weather through the lens of AI-generated art,
bringing a new perspective to daily meteorological phenomena.
button:
explore_cities: "Explore Cities"
view_detail: "View Details"
view_all_weather_arts: "View All Weather Arts"
back_to_cities: "Back to Cities"
back_to: "Back to"
pagination:
showing_items: "Showing %{from} to %{to} of %{total} %{items}"
items:
weather: "weather records"
default: "items"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"

48
config/locales/zh-CN.yml Normal file
View File

@ -0,0 +1,48 @@
zh-CN:
hello: "你好"
brand:
name: "全球艺术天气"
title:
cities: "城市探索"
arts: "艺术巡览"
sign_in: "用户登录"
sign_out: "退出登录"
settings: "系统设置"
admin_dashboard: "管理中枢"
latest_weather_art: "气象绘卷·新作"
popular_weather_art: "气象绘卷·佳作"
text:
latest_from: "新至之城"
search_cities: "寻城觅境…"
all_regions: "寰宇之境"
showing: "映现"
weather_arts: "气象艺境"
cities:
title: "云游四海"
arts:
title: "天象画廊"
subtitle: "邂逅寰宇都市AI气象绘卷"
home:
headline_html: 当气象邂逅<br>人工智能之美
subtitle:
通过AI生成的艺术视角感受气象为日常天气现象带来全新解读。
button:
explore_cities: "云游四海"
view_detail: "详阅此卷"
view_all_weather_arts: "尽览天工"
back_to_cities: "继续探索城市"
back_to: "回到"
pagination:
showing_items: "显示第 %{from} 到第 %{to} 条,共 %{total} 条%{items}"
items:
weather: "天气记录"
default: "记录"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
short: "%Y-%m-%d"
long: "%Y 年 %m 月 %d 日"

View File

@ -1,50 +1,52 @@
require "sidekiq/web" require "sidekiq/web"
Rails.application.routes.draw do Rails.application.routes.draw do
devise_for :users scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do
root "home#index" devise_for :users
root "home#index"
resources :cities, only: [ :index, :show ] do resources :cities, only: [ :index, :show ] do
resources :weather_arts, path: "weather", only: [ :show ], param: :slug resources :weather_arts, path: "weather", only: [ :show ], param: :slug
end
resources :cities do
member do
post :generate_weather_art, param: :slug
end end
resources :cities do
member do
post :generate_weather_art, param: :slug
end
end
resources :arts, only: [ :index ]
# namespace :admin do
# resources :cities
# resources :weather_arts
# root to: "cities#index"
# end
get "weather_arts/show"
get "cities/index"
get "cities/show"
get "home/index"
get "sitemaps/*path", to: "sitemaps#show", format: false
get "feed", to: "rss#feed", format: "rss", as: :rss_feed
devise_for :admin_users, ActiveAdmin::Devise.config
ActiveAdmin.routes(self)
# mount Sidekiq::Web => '/sidekiq'
# authenticate :admin_user do
authenticate :user, lambda { |u| u.admin? } do
mount Sidekiq::Web => "/admin/tasks"
end
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
# Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb)
# get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
# Defines the root path route ("/")
# root "posts#index"
end end
resources :arts, only: [ :index ]
# namespace :admin do
# resources :cities
# resources :weather_arts
# root to: "cities#index"
# end
get "weather_arts/show"
get "cities/index"
get "cities/show"
get "home/index"
get "sitemaps/*path", to: "sitemaps#show", format: false
get "feed", to: "rss#feed", format: "rss", as: :rss_feed
devise_for :admin_users, ActiveAdmin::Devise.config
ActiveAdmin.routes(self)
# mount Sidekiq::Web => '/sidekiq'
# authenticate :admin_user do
authenticate :user, lambda { |u| u.admin? } do
mount Sidekiq::Web => "/admin/tasks"
end
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
# Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb)
# get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
# Defines the root path route ("/")
# root "posts#index"
end end