Compare commits

..

26 Commits

Author SHA1 Message Date
fe5c0d5113 fix: update sitemap retrieval logic
Some checks failed
Docker Dev / docker (push) Has been cancelled
CI / scan_ruby (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
Docker Main / docker (push) Has been cancelled
- Change from using `get_object` to `head_object` to check if the
  sitemap file exists before attempting to retrieve it.
- Implement a presigned URL for accessing the sitemap, which is valid
  for 15 minutes.
- Set cache headers to allow for 1 hour of caching.
- Improved logging for better error tracking and debugging.

This change enhances the efficiency of sitemap retrieval by reducing
unnecessary data transfer and provides a more secure way to access
the sitemaps through presigned URLs. It also improves error handling
by logging specific errors related to missing sitemaps.
2025-02-24 17:42:55 +08:00
3ae870047a feat: add sitemap management feature
- Implement index action to list sitemaps
- Create view for displaying sitemaps with details
- Add helper method for generating sitemap URLs
- Enhance error handling for S3 service errors

This commit introduces a new feature for managing sitemaps in the application. It includes an index view that lists all available sitemaps with their last modified date and size, along with a link to view each sitemap. The error handling for S3 interactions has also been improved to log errors and return appropriate responses.
2025-02-24 17:28:21 +08:00
2a360a6875 style: fix formatting in Arabic locale file
- Ensure consistent formatting in the long date string
- No functional changes were made, only a formatting adjustment

This commit addresses a minor formatting issue in the Arabic locale file, ensuring that the long date format is consistent with the expected output. No changes to functionality or behavior were introduced.
2025-02-24 17:09:21 +08:00
bd04bb63a1 feat: add support for multiple languages
- Update available locales to include Bengali, Hindi, Urdu, and Arabic
- Add new locale files for each language with translations for UI elements
- Ensure the application can now support a wider audience by providing
  localized content

This change enhances the application's accessibility and usability for
users who speak these languages, allowing for a more inclusive user
experience. The new translations cover key UI components and messages,
ensuring that users can interact with the application in their native
languages.
2025-02-24 17:04:23 +08:00
5f98d9ebfd feat: enhance language switcher dropdown
- Update dropdown to support dynamic locales from I18n
- Limit dropdown height with overflow handling for better UX

This change improves the language switcher by dynamically
loading available locales from the I18n configuration. It
also enhances the user experience by limiting the height
of the dropdown and enabling scrolling, making it easier
to navigate through multiple language options.
2025-02-24 15:55:01 +08:00
9ef2a92d60 feat: add multiple language support for locales
- Extend available locales in the application to include:
  - Portuguese (Brazil)
  - Croatian
  - Persian
  - German
  - Spanish
  - French
  - Italian
  - Turkish
  - Russian
  - Ukrainian
  - Polish

- Create new locale files for each language with appropriate translations.
- Update existing locale files to include new languages.

This update enhances the application's accessibility by supporting a wider range of languages, allowing users from different regions to interact with the application in their native language.
2025-02-24 15:46:29 +08:00
03c957e654 feat: add date formats to localization
- Introduce default, short, and long date formats
- Enhance date representation for better user experience

This update allows the application to display dates in multiple formats,
including a default format of 'YYYY-MM-DD', a short format of
'"MMM DD"', and a long format of '"Month DD, YYYY"'. This
improves localization support and user interface flexibility.
2025-02-24 14:20:49 +08:00
b2cc7e7016 chore: update keywords for SEO optimization
- Refactor keywords in cities_controller.rb
- Refactor keywords in weather_arts_controller.rb
- Refactor keywords in application.html.erb

This change improves the SEO of the application by updating
keywords to include 'ai' and 'ai web', which are more relevant
and likely to enhance search visibility. The previous keywords
were less optimized for current trends in AI-related searches.
2025-02-24 14:12:51 +08:00
da2f4f6c86 fix: update keywords for better SEO
- Modify keywords in cities_controller.rb to include 'ai weather'
- Update keywords in weather_arts_controller.rb to include 'ai weather'
- Change keywords in application.html.erb to include 'ai weather'

These changes enhance the search engine optimization (SEO) of the
application by ensuring that relevant keywords are included in
meta tags, improving visibility for users searching for AI
weather-related content.
2025-02-24 14:10:32 +08:00
3661d2b008 chore: update meta keywords for SEO
- Modify keywords in CitiesController for better
  categorization of AI art and weather art.
- Update keywords in WeatherArtsController to include
  city country name and description.
- Add keywords in application layout for overall site
  SEO improvement.

These changes aim to improve search engine visibility
and better describe the content related to AI-generated
weather art.
2025-02-24 14:08:25 +08:00
0e476b546d feat: add locking mechanism to batch task worker
Some checks failed
Docker Dev / docker (push) Has been cancelled
CI / scan_ruby (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
Docker Main / docker (push) Has been cancelled
- Introduce a Redis-based lock to prevent concurrent execution of
  batch generation tasks.
- Set a TTL of 300 seconds for the lock to ensure it is released
  after a timeout.
- Add logging for situations where a task is already in progress.

This enhancement ensures that batch tasks do not overlap, which can
lead to data inconsistencies and resource contention. The locking
mechanism improves the reliability of the batch processing system.
2025-02-22 15:41:42 +08:00
d331a73a85 fix: update html lang attribute for I18n
- Change the lang attribute of the HTML tag to use the current I18n locale
- This change ensures that the application correctly reflects the user's language preferences

Updating this attribute improves accessibility and SEO by helping search engines and assistive technologies better understand the language of the content.
2025-02-22 15:19:40 +08:00
5a82fc9a10 style: correct string split style
- Change string split method from single quotes to double quotes.

This commit improves code consistency by aligning the string
split syntax with the rest of the codebase. No functional
changes were made as a result of this update.
2025-02-22 15:00:37 +08:00
926ba18e85 refactor: change locale handling to around_action
- Update set_locale method to use around_action instead of before_action.
- Modify locale extraction logic to handle more cases, ensuring better fallback handling.
- Improve overall method clarity and maintainability by restructuring code.

This change enhances the localization process by providing a clearer
way to manage locale settings and ensures that it correctly falls back
to the default locale when necessary. It also resolves some edge cases
in locale extraction based on the HTTP_ACCEPT_LANGUAGE header.
2025-02-22 15:00:22 +08:00
09fa1ceea9 feat: update sitemap generation for multiple locales
- Refactor generate_sitemap method to support locale
- Add setup_sitemap_config method for configuration
- Implement sitemap generation for default and localized paths

This update enhances the sitemap generation process by supporting
multiple languages. Users can now access a sitemap with language
prefixes, improving SEO and usability for different locales.
Additionally, error handling has been improved to log specific
errors related to locale generation.
2025-02-22 12:08:59 +08:00
5b996bb64a style: update layout and improve accessibility
Some checks are pending
Docker Dev / docker (push) Waiting to run
- Adjust footer components for better spacing
- Move language switcher to navbar for easier access
- Simplify language switcher links using iteration
- Enhance copyright text to dynamically reflect the current year
2025-02-22 01:03:40 +08:00
80ceac5d94 feat: add locale extraction and sanitization methods
- Implemented `extract_locale_from_accept_language_header` to
  retrieve the user's preferred language from the request.
- Added `sanitize_locale` for validating and sanitizing locale
  inputs against available locales.
- Updated `set_locale` method to prepare for incorporating
  browser language preference handling.

These changes enhance the application's ability to set the locale
based on the user's browser settings, paving the way for better
internationalization support.
2025-02-22 00:13:35 +08:00
bd42833953 feat: add translatable name module for countries and regions
- Introduced `TranslatableName` module to allow for
  localized names for `Country` and `Region` models.
- Updated views to display `localized_name` instead of
  `name` for improved internationalization.
- Refactored JSON serialization for `translations` attribute.
- Enhanced localization support by adding new languages:
  Japanese and Korean, with updated locale files.
- Removed outdated English and Chinese locales for countries
  and regions to clean up the codebase.
2025-02-21 23:46:25 +08:00
f6b9dcf187 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.
2025-02-21 17:51:25 +08:00
517e3038cc chore: rename docker workflows and clean up city seeds
- Rename docker workflow files for clarity: 'docker-dev' and 'docker-main'
- Remove unused city seed files to streamline the database seeding process

These changes improve the organization of the workflow files and reduce clutter in the seed data, making it easier to manage and maintain the project.
2025-02-21 10:02:36 +08:00
9fe92b1fc4 feat: add RSS feed functionality
Some checks failed
Docker / docker (push) Has been cancelled
- Introduce RssController to handle RSS feed requests
- Add a new route for the RSS feed
- Implement RSS feed view to display weather art
- Update application layout to include RSS feed link
- Set content type for RSS responses

This commit adds an RSS feed feature that allows users to
subscribe to updates on daily AI-generated weather art.
The feed includes the latest weather art and relevant
metadata, enhancing user engagement and accessibility.
2025-02-19 17:38:49 +08:00
c35f09660a fix: handle nil safely for latest arts image
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
Docker / docker (push) Has been cancelled
- Update image tag to safely access the first latest art
- Use safe navigation operator to prevent potential nil errors

This change ensures that if there are no latest arts or if the
image is not attached, the application will not raise an error.
It improves the robustness of the view by handling edge cases.
2025-02-19 14:18:14 +08:00
789e9f8d23 feat: migrate AWS configuration to MinIO
- Update SitemapsController to use MinIO credentials
- Modify RefreshSitemapWorker to adapt to MinIO settings
- Change AWS configuration in initializers and storage files
- Add MinIO credentials to credentials.yml.enc

This commit transitions the application from using AWS S3 to MinIO for
storage. It updates all relevant configurations and ensures that the
application can now interact with MinIO seamlessly, including
support for both production and development environments.
2025-02-19 11:31:57 +08:00
468a665354 style: update badge classes for weather art display
Some checks are pending
Docker / docker (push) Waiting to run
CI / scan_ruby (push) Waiting to run
CI / lint (push) Waiting to run
CI / test (push) Waiting to run
- Change badge class from 'badge-neutral' to 'badge-ghost' for
  better visual consistency.
- This update improves the UI by aligning the badge styles with
  the overall design language of the application.
2025-02-18 17:00:49 +08:00
9a02115562 feat: enhance weather art display layout
- Update background color for better visibility
- Adjust padding and margins for improved spacing
- Refactor card components for a cleaner design
- Add AI Prompt section for better user engagement

These changes improve the overall user experience by making the
weather art display more visually appealing and easier to
navigate. The layout adjustments also enhance the mobile
view, ensuring a consistent experience across devices.
2025-02-18 16:54:17 +08:00
fc721ada9f Refactor: Limit images generation
Some checks are pending
Docker / docker (push) Waiting to run
CI / scan_ruby (push) Waiting to run
CI / lint (push) Waiting to run
CI / test (push) Waiting to run
- Modify the `PER_RUN_ION` constant of `BatchGenerateWeatherArtsWorker` from
  3 to 2, reducing the number of images generated on each run.

- This change reduces the limit and will not affect other parts
  of the application.
- It is about reducing the number of images generated per run and
  will impact the performance of the application.
- It will not affect the other parts.
</commit_message>
2025-02-17 23:21:04 +08:00
91 changed files with 2096 additions and 1210 deletions

View File

@ -1,4 +1,4 @@
name: Docker
name: Docker Dev
on:
push:
@ -45,4 +45,4 @@ jobs:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: |
${{ env.REGISTRY }}/${{github.actor}}/${{ github.event.repository.name }}:dev
${{ env.REGISTRY }}/${{github.actor}}/${{ github.event.repository.name }}_dev:dev

View File

@ -1,4 +1,4 @@
name: Docker
name: Docker Main
on:
push:

View File

@ -1 +1 @@
ruby-3.3.5
3.3.5

View File

@ -61,12 +61,14 @@ gem "aws-sdk-core", "3.211"
gem "sidekiq", "~> 7.3"
gem "sidekiq-scheduler", "~> 5.0"
gem "image_processing", "~> 1.14"
gem "image_processing", "~> 1.13"
# gem "ruby-vips", "~> 2.2"
gem "mini_magick", "~> 4.13.2"
gem "redis", "~> 5.3"
gem "builder", "~> 3.3"
group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"

View File

@ -194,8 +194,8 @@ GEM
multi_xml (>= 0.5.2)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
image_processing (1.14.0)
mini_magick (>= 4.9.5, < 6)
image_processing (1.13.0)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
inherited_resources (1.14.0)
actionpack (>= 6.0)
@ -242,7 +242,7 @@ GEM
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
language_server-protocol (3.17.0.4)
logger (1.6.6)
logger (1.6.5)
loofah (2.24.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
@ -409,7 +409,7 @@ GEM
faraday (>= 1)
faraday-multipart (>= 1)
ruby-progressbar (1.13.0)
ruby-vips (2.2.3)
ruby-vips (2.2.2)
ffi (~> 1.12)
logger
ruby2_keywords (0.0.5)
@ -523,6 +523,7 @@ DEPENDENCIES
aws-sdk-s3 (~> 1.170)
bootsnap
brakeman
builder (~> 3.3)
bullet (~> 8.0)
capybara
cssbundling-rails
@ -531,7 +532,7 @@ DEPENDENCIES
down (~> 5.4)
friendly_id (~> 5.5)
httparty (~> 0.22.0)
image_processing (~> 1.14)
image_processing (~> 1.13)
jbuilder
jsbundling-rails
kamal

View File

@ -2,6 +2,7 @@ class ApplicationController < ActionController::Base
include SeoConcern
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
before_action :log_browser_info
before_action :set_content_type_for_rss, if: -> { request.format.rss? }
# allow_browser versions: :modern
# allow_browser versions: :modern,
# patterns: [
@ -47,7 +48,7 @@ class ApplicationController < ActionController::Base
# Bot: #{browser.bot?}
# BROWSER_INFO
# }
before_action :set_locale
around_action :set_locale
after_action :track_action
def log_browser_info
@ -75,7 +76,55 @@ class ApplicationController < ActionController::Base
private
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
def set_locale(&action)
I18n.locale = extract_locale || I18n.default_locale
I18n.fallbacks[I18n.locale] = [ I18n.locale, I18n.default_locale ].uniq
locale = params[:locale] || extract_locale_from_accept_language_header || I18n.default_locale
I18n.with_locale(locale, &action)
# 重定向到带有语言前缀的相同路径
# redirect_to "/#{locale}#{request.fullpath}"
end
def extract_locale_from_accept_language_header
return I18n.default_locale.to_s unless request.env["HTTP_ACCEPT_LANGUAGE"]
available_locales = I18n.available_locales.map(&:to_s)
accept_language = request.env["HTTP_ACCEPT_LANGUAGE"].to_s
# 修改正则表达式以匹配 'zh-CN' 这样的格式
if (full_locale = accept_language.scan(/^[a-z]{2}-[A-Z]{2}/).first)
locale = full_locale
else
# 否则只匹配语言代码 (例如 'en')
locale = accept_language.scan(/^[a-z]{2}/).first || I18n.default_locale.to_s
end
return locale if available_locales.include?(locale)
# 尝试基础语言匹配(例如:当请求 'zh' 时匹配 'zh-CN'
base_language = locale.split("-").first
matching_locale = available_locales.find do |available_locale|
available_locale.start_with?(base_language)
end
matching_locale ? matching_locale : I18n.default_locale.to_s
end
def sanitize_locale(locale)
# 直接使用 I18n.available_locales
locale = locale.to_sym
I18n.available_locales.include?(locale) ? locale : I18n.default_locale
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
def set_content_type_for_rss
response.headers["Content-Type"] = "application/rss+xml; charset=utf-8"
end
end

View File

@ -46,7 +46,7 @@ class CitiesController < ApplicationController
set_meta_tags(
title: @city.name,
description: "Experience #{@city.name}'s weather through AI-generated art. Daily updates of weather conditions visualized through artificial intelligence.",
keywords: "#{@city.name}, #{@city.country.name}, weather art, AI visualization",
keywords: "#{@city.name}, #{@city.country.name}, ai, ai web, ai art, ai weather, weather art, AI visualization",
og: {
image: @city.latest_weather_art&.image&.attached? ? url_for(@city.latest_weather_art.image) : nil
}

View File

@ -0,0 +1,9 @@
class RssController < ApplicationController
def feed
@weather_arts = WeatherArt.order(created_at: :desc).limit(20)
respond_to do |format|
format.rss { render layout: false }
end
end
end

View File

@ -1,33 +1,126 @@
class SitemapsController < ApplicationController
include SitemapsHelper
before_action :set_bucket_name
def index
@sitemaps = list_sitemaps
respond_to do |format|
format.html
format.xml { render_sitemap_index }
end
rescue Aws::S3::Errors::ServiceError => e
Rails.logger.error "S3 Error: #{e.message}"
render status: :internal_server_error
end
def show
path = params[:path]
bucket_name =
Rails.env.production? ?
ENV.fetch("AWS_BUCKET", Rails.application.credentials.dig(:aws, :bucket)) :
ENV.fetch("AWS_DEV_BUCKET", Rails.application.credentials.dig(:aws_dev, :bucket))
Rails.logger.info "Sitemap: #{path}"
key = "sitemaps/#{path}"
Rails.logger.info "Requesting sitemap: #{path}"
begin
s3_client = Aws::S3::Client.new
response = s3_client.get_object(
bucket: bucket_name,
key: "sitemaps/#{path}"
# 检查文件是否存在
s3_client.head_object(
bucket: @bucket_name,
key: key
)
expires_in 12.hours, public: true
content_type = response.content_type || "application/xml"
send_data(
response.body.read,
filename: path,
type: content_type,
disposition: "inline"
# 生成预签名URL设置15分钟有效期
signer = Aws::S3::Presigner.new(client: s3_client)
url = signer.presigned_url(
:get_object,
bucket: @bucket_name,
key: key,
expires_in: 15 * 60, # 15 minutes
# response_content_type: 'application/xml', # 确保正确的内容类型
response_content_disposition: "inline" # 在浏览器中直接显示
)
rescue Aws::S3::Errors::NoSuchKey
# 设置缓存头
response.headers["Cache-Control"] = "public, max-age=3600" # 1小时缓存
# 重定向到预签名URL
redirect_to url, allow_other_host: true, status: :found
rescue Aws::S3::Errors::NotFound
Rails.logger.error "Sitemap not found: #{path}"
render status: :not_found
rescue Aws::S3::Errors::ServiceError => e
Rails.logger.error "S3 Error: #{e.message}"
render status: :internal_server_error
end
end
# def show
# path = params[:path]
# Rails.logger.info "Sitemap: #{path}"
# begin
# response = s3_client.get_object(
# bucket: @bucket_name,
# key: "sitemaps/#{path}"
# )
# expires_in 12.hours, public: true
# content_type = response.content_type || "application/xml"
# send_data(
# response.body.read,
# filename: path,
# type: content_type,
# disposition: "inline"
# )
# rescue Aws::S3::Errors::NoSuchKey
# render status: :not_found
# rescue Aws::S3::Errors::ServiceError => e
# Rails.logger.error "S3 Error: #{e.message}"
# render status: :internal_server_error
# end
# end
private
def set_bucket_name
@bucket_name = Rails.env.production? ?
ENV.fetch("AWS_BUCKET", Rails.application.credentials.dig(:minio, :bucket)) :
ENV.fetch("AWS_DEV_BUCKET", Rails.application.credentials.dig(:minio_dev, :bucket))
end
def s3_client
@s3_client ||= Aws::S3::Client.new
end
def list_sitemaps
response = s3_client.list_objects_v2(
bucket: @bucket_name,
prefix: "sitemaps/"
)
response.contents.map do |object|
{
key: object.key.sub("sitemaps/", ""),
last_modified: object.last_modified,
size: object.size,
url: sitemap_url(object.key.sub("sitemaps/", ""))
}
end.reject { |obj| obj[:key].empty? }
end
def render_sitemap_index
base_url = "#{request.protocol}#{request.host_with_port}"
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
xml.sitemapindex(xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9") do
@sitemaps.each do |sitemap|
xml.sitemap do
xml.loc "#{base_url}/sitemaps/#{sitemap[:key]}"
xml.lastmod sitemap[:last_modified].iso8601
end
end
end
end
render xml: builder.to_xml
end
end

View File

@ -17,7 +17,7 @@ class WeatherArtsController < ApplicationController
set_meta_tags(
title: "#{@city.name} Weather Art - #{@weather_art.weather_date.strftime('%B %d, %Y')}",
description: "#{@city.name}'s weather visualized through AI art. #{@weather_art.description} at #{@weather_art.temperature}°C.",
keywords: "#{@city.name}, weather art, #{@weather_art.description}, AI visualization",
keywords: "#{@city.name}, #{@city.country.name}, ai, ai web, ai art, ai weather, weather art, AI visualization, #{@weather_art.description}",
og: {
image: @weather_art.image.attached? ? url_for(@weather_art.image) : nil
}

View File

@ -0,0 +1,2 @@
module RssHelper
end

View File

@ -1,2 +1,5 @@
module SitemapsHelper
def sitemap_url(filename)
"/sitemaps/#{filename}"
end
end

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -79,11 +79,23 @@ class WeatherArt < ApplicationRecord
ActiveSupport::TimeZone["UTC"]
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 =
use_local_timezone ?
"#{time.in_time_zone(time_zone).strftime('%H:%M')} #{timezone_info['gmtOffsetName']}" :
"#{time.utc.strftime('%H:%M')} UTC"
if use_local_timezone
I18n.t("time.formats.with_zone",
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
when :date
@ -91,9 +103,15 @@ class WeatherArt < ApplicationRecord
when :time
time_string
when :all
"#{date_string} #{time_string}"
I18n.t("time.formats.date_and_time",
date: date_string,
time: time_string
)
else
"#{date_string} #{time_string}"
I18n.t("time.formats.date_and_time",
date: date_string,
time: time_string
)
end
end

View File

@ -17,16 +17,16 @@
<div class="container mx-auto px-4">
<div class="max-w-3xl mx-auto text-center space-y-6">
<h1 class="text-4xl md:text-5xl font-display font-bold">
Weather Arts Gallery
<%= t("arts.title") %>
</h1>
<p class="text-xl text-base-content/70">
Discover AI-generated weather art from cities around the world
<%= t("arts.subtitle") %>
</p>
<!-- 如果有特色图片,显示其信息 -->
<% if featured_art %>
<div class="text-sm text-base-content/60 pt-4">
Latest from <%= featured_art.city.name %>, <%= featured_art.city.country.name %>
<%= "#{t("text.latest_from")} #{featured_art.city.full_name}" %>
<span class="mx-2">•</span>
<%= featured_art.formatted_time(:date) %>
</div>
@ -47,18 +47,18 @@
<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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<%= params[:sort] == 'oldest' ? 'Oldest First' : 'Newest First' %>
<%= params[:sort] == 'oldest' ? t("text.oldest_first") : t("text.newest_first") %>
<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" />
</svg>
</button>
<ul class="dropdown-content z-[1] menu p-2 shadow-lg bg-base-100 rounded-box w-52">
<li>
<%= link_to "Newest First", arts_path(sort: 'newest', region: params[:region]),
<%= link_to t("text.newest_first"), arts_path(sort: 'newest', region: params[:region]),
class: "#{'active' if params[:sort] != 'oldest'}" %>
</li>
<li>
<%= link_to "Oldest First", arts_path(sort: 'oldest', region: params[:region]),
<%= link_to t("text.oldest_first"), arts_path(sort: 'oldest', region: params[:region]),
class: "#{'active' if params[:sort] == 'oldest'}" %>
</li>
</ul>
@ -70,20 +70,20 @@
<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" />
</svg>
<%= @current_region&.name || 'All Regions' %>
<%= @current_region&.localized_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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<ul class="dropdown-content z-[1] menu p-2 shadow-lg bg-base-100 rounded-box w-52">
<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}" %>
</li>
<div class="divider my-1"></div>
<% @regions.each do |region| %>
<li>
<%= 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}" %>
</li>
<% end %>
@ -93,9 +93,9 @@
<!-- 结果统计 -->
<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 %>
from <%= @current_region.name %>
from <%= @current_region.localized_name %>
<% end %>
</div>
</div>
@ -118,7 +118,7 @@
<%= art.city.name %>
</h3>
<p class="text-sm text-white/80">
<%= art.city.country.name %>
<%= "#{art.city&.country&.emoji + " " || ""}#{art.city&.country&.localized_name}" %>
</p>
<div class="flex items-center gap-2 text-white/90">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -154,7 +154,7 @@
<%= link_to city_weather_art_path(art.city, art),
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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>

View File

@ -75,7 +75,7 @@
<div class="card-actions justify-end">
<%= link_to city_path(city),
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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
@ -115,7 +115,7 @@
<div class="card-actions justify-end">
<%= link_to city_path(city),
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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>

View File

@ -18,7 +18,7 @@
<%= f.text_field :query,
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",
placeholder: "Search cities...",
placeholder: t("text.search_cities"),
autocomplete: "off",
data: {
action: "input->search#submit",

View File

@ -16,18 +16,18 @@
<div class="container mx-auto px-4">
<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">
Explore Cities
<%= t("cities.title") %>
</h1>
<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>
<!-- 特色图片信息 -->
<% if featured_art %>
<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>,
<%= featured_art.city.country.name %>
<%= featured_art.city.country.localized_name %>
<span class="mx-2">•</span>
<%= featured_art.formatted_time(:date) %>
</div>
@ -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">
<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>
<%= @current_region&.name || 'All Regions' %>
<%= @current_region&.localized_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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
@ -59,13 +59,13 @@
<li>
<%= link_to cities_path,
class: "#{@current_region ? '' : 'active'}" do %>
All Regions
<%= t("text.all_regions") %>
<% end %>
</li>
<div class="divider my-1"></div>
<% @regions.each do |region| %>
<li>
<%= link_to region.name,
<%= link_to region.localized_name,
cities_path(region: region.slug),
class: "#{@current_region == region ? 'active' : ''}" %>
</li>
@ -79,21 +79,21 @@
<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 21v-4m0 0V5a2 2 0 012-2h6.5l1 1H21l-3 6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9" />
</svg>
<%= @current_country&.name || "All Countries" %>
<%= @current_country&.localized_name || t("text.all_countries") %>
<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" />
</svg>
</button>
<ul class="dropdown-content z-[1] menu p-2 shadow-lg bg-base-100 rounded-box w-52 max-h-80 overflow-y-auto flex-nowrap">
<li>
<%= link_to "All in #{@current_region.name}",
<%= link_to "#{t("text.all_in")} #{@current_region.localized_name}",
cities_path(region: @current_region.slug),
class: "#{@current_country ? '' : 'active'}" %>
</li>
<div class="divider my-1"></div>
<% @current_region.countries.order(:name).each do |country| %>
<li>
<%= link_to "#{country&.emoji + " " || ""}#{country.name}",
<%= link_to "#{country&.emoji + " " || ""}#{country.localized_name}",
cities_path(region: @current_region.slug, country: country.slug),
class: "#{@current_country == country ? 'active' : ''}" %>
</li>
@ -106,9 +106,9 @@
<div class="text-sm text-base-content/70 hidden">
<%= @cities.count %> <%= 'city'.pluralize(@cities.count) %>
<% if @current_country %>
in <%= @current_country.name %>
in <%= @current_country.localized_name %>
<% elsif @current_region %>
in <%= @current_region.name %>
in <%= @current_region.localized_name %>
<% end %>
</div>
</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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
Back to Cities
<%= t("button.back_to_cities") %>
<% end %>
</div>

View File

@ -20,7 +20,7 @@
</div>
</div>
<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" %>
</div>
</div>

View File

@ -3,7 +3,7 @@
<section class="h-screen-90 relative overflow-hidden">
<% if @latest_arts.first&.image&.attached? %>
<div class="absolute inset-0">
<%= image_tag @latest_arts.first.webp_image.processed, class: "w-full h-full object-cover" %>
<%= image_tag @latest_arts&.first&.webp_image&.processed, class: "w-full h-full object-cover" %>
<div class="absolute inset-0 bg-gradient-to-r from-base-100/90 to-base-100/50"></div>
</div>
<% end %>
@ -11,13 +11,12 @@
<div class="container mx-auto px-4 h-full flex items-center relative">
<div class="max-w-2xl space-y-6">
<h1 class="text-5xl md:text-6xl font-display font-bold leading-tight">
Where Weather Meets<br>Artificial Intelligence
<%= t("home.headline_html") %>
</h1>
<p class="text-xl text-base-content/70 font-sans">
Experience weather through the lens of AI-generated art,
bringing a new perspective to daily meteorological phenomena.
<%= t("home.subtitle") %>
</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" %>
</div>
</div>
@ -25,9 +24,9 @@
<!-- 最新天气艺术 -->
<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 %>
<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 %>
<!-- <h2 class="text-3xl font-display font-bold text-center">Random Weather Art</h2>-->
<%#= render 'home/arts', arts: @random_arts %>
@ -35,7 +34,7 @@
</div>
<div class="text-center mt-12 mb-12">
<%= 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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>

View File

@ -1,6 +1,18 @@
<!-- 页脚 -->
<footer class="footer footer-center p-8 bg-base-200 text-base-content">
<div class="container mx-auto flex flex-col gap-2">
<div class="container mx-auto flex flex-col gap-4">
<!-- 第一行:功能按钮 -->
<div class="flex items-center justify-center space-x-4">
<%= link_to rss_feed_path(format: :rss), class: "btn btn-ghost btn-sm", title: "RSS Feed" do %>
<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="M6 5c7.18 0 13 5.82 13 13M6 11a7 7 0 017 7m-6 0a1 1 0 11-2 0 1 1 0 012 0z" />
</svg>
<% end %>
<%= render 'shared/language_switcher' %>
</div>
<!-- 第二行:统计信息 -->
<div id="busuanzi_container" class="text-xs opacity-70">
<div class="space-x-2">
<span>Page: </span>
@ -10,13 +22,14 @@
<span>PV <span id="busuanzi_site_pv"></span></span>
<span>UV <span id="busuanzi_site_uv"></span></span>
<span data-controller="page-load-time">
Page Load Time: <span data-page-load-time-target="timer">x</span> ms
Load Time: <span data-page-load-time-target="timer">x</span> ms
</span>
</div>
</div>
<p class="font-display opacity-80">
Copyright © 2024 - All rights reserved by AI Weather Art
<!-- 第三行:版权信息 -->
<p class="font-display opacity-80 text-sm">
Copyright © <%= Time.current.year %> - All rights reserved by AI Weather Art
</p>
</div>
</footer>
</footer>

View File

@ -3,14 +3,14 @@
<!-- Logo -->
<div class="flex-1">
<%= 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 %>
</div>
<!-- Desktop Menu -->
<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 "Arts", arts_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 t("title.arts"), arts_path, class: "btn btn-ghost btn-sm font-sans" %>
<% if user_signed_in? %>
<div class="dropdown dropdown-end">
@ -27,12 +27,14 @@
</label>
<%= render 'layouts/user_menu' %>
</div>
<%= render 'shared/language_switcher' %>
<% else %>
<%= 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">
<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>
<span>Sign in</span>
<span><%= t("title.sign_in") %></span>
<% end %>
<% end %>
</div>
@ -46,8 +48,8 @@
</svg>
</label>
<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 "Arts", arts_path %></li>
<li><%= link_to t("title.cities"), cities_path %></li>
<li><%= link_to t("title.arts"), arts_path %></li>
<div class="divider my-1"></div>
<% if user_signed_in? %>
<li>
@ -56,7 +58,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="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Settings
<%= t("title.settings") %>
<% end %>
</li>
<li>
@ -67,7 +69,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">
<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>
<span>Sign out</span>
<span><% t("title.sign_out") %></span>
</div>
<% end %>
</li>
@ -77,7 +79,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">
<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>
Sign in
<%= t("title.sign_in") %>
<% end %>
</li>
<% 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="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span>Settings</span>
<span><%= t("title.settings") %></span>
<% end %>
</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">
<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>
<span>Admin Dashboard</span>
<span><%= t("title.admin_dashboard") %></span>
<% end %>
</li>
<% 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">
<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>
<span>Sign out</span>
<span><%= t("title.sign_out") %></span>
<% end %>
</li>
</ul>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="<%= I18n.locale %>">
<head>
<title><%= content_for(:title) || "Today Ai Weather" %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
@ -11,11 +11,14 @@
og: {
site_name: 'TodayAIWeather',
type: 'website',
keywords: "ai, ai web, ai art, ai weather, weather art, AI visualization, today ai weather",
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 %>
@ -33,6 +36,8 @@
<%# Includes all stylesheet files in app/assets/stylesheets %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= auto_discovery_link_tag :rss, rss_feed_url(format: :rss), title: 'RSS Feed' %>
<script defer data-domain="todayaiweather.com" src="https://plausible.frytea.com/js/script.js"></script>
<script defer src="https://busuanzi.frytea.com/js"></script>

View File

@ -0,0 +1,24 @@
# app/views/rss/feed.rss.builder
xml.instruct! :xml, version: "1.0"
xml.rss version: "2.0",
"xmlns:atom" => "http://www.w3.org/2005/Atom" do
xml.channel do
xml.title "Today AI Weather Art"
xml.description "Daily AI-generated weather art and forecasts"
xml.link root_url
xml.language "en"
xml.atom :link, href: rss_feed_url(format: :rss), rel: "self", type: "application/rss+xml"
@weather_arts.each do |art|
xml.item do
xml.title "#{art.city.full_name} Weather Art"
xml.description art.description
xml.pubDate art.created_at.to_fs(:rfc822)
xml.link city_weather_art_url(art.city, art)
xml.guid city_weather_art_url(art.city, art)
# 如果有图片,添加图片链接
xml.enclosure url: rails_blob_url(art.webp_image.processed), type: "image/jpeg" if art.image.attached?
end
end
end
end

View File

@ -0,0 +1,17 @@
<%# app/views/shared/_language_switcher.html.erb %>
<div class="dropdown dropdown-top">
<label tabindex="0" class="btn btn-ghost btn-sm">
<%= t("language.#{I18n.locale}") %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</label>
<ul tabindex="0" class="dropdown-content menu p-2 max-h-80 overflow-y-auto flex-nowrap shadow bg-base-100 rounded-box w-32">
<% I18n.available_locales.each do |locale| %>
<%= link_to url_for(locale: locale),
class: "px-4 py-2 hover:bg-base-200 rounded-lg #{I18n.locale == locale ? 'bg-base-200' : ''}" do %>
<%= t("language.#{locale}") %>
<% end %>
<% end %>
</ul>
</div>

View File

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

View File

@ -0,0 +1,43 @@
<%# app/views/sitemaps/index.html.erb %>
<div class="container mx-auto px-4 py-8">
<div class="max-w-4xl mx-auto">
<h1 class="text-3xl font-bold mb-6">Sitemaps Index</h1>
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="overflow-x-auto">
<table class="table w-full">
<thead>
<tr>
<th>Filename</th>
<th>Last Modified</th>
<th>Size</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<% @sitemaps.each do |sitemap| %>
<tr class="hover">
<td><%= sitemap[:key] %></td>
<td><%= sitemap[:last_modified].strftime("%Y-%m-%d %H:%M:%S") %></td>
<td><%= number_to_human_size(sitemap[:size]) %></td>
<td>
<%= link_to "View", sitemap[:url],
class: "btn btn-sm btn-primary",
target: "_blank" %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<div class="mt-6 bg-base-200 p-4 rounded-lg">
<h2 class="text-xl font-semibold mb-2">For Search Engines</h2>
<p class="mb-2">Sitemap Index URL:</p>
<code class="block bg-base-300 p-2 rounded">
<%= sitemaps_url(format: :xml) %>
</code>
</div>
</div>
</div>

View File

@ -35,7 +35,7 @@
<%= link_to city_weather_art_path(weather_art.city, weather_art),
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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>

View File

@ -1,37 +1,37 @@
<%# Partial _weather_stats.html.erb %>
<div class="stat bg-gradient-to-br from-primary/10 to-primary/20 hover:from-primary hover:to-primary/30 p-4 rounded-lg">
<div class="stat-title font-medium text-base">Temperature</div>
<div class="stat-title font-medium text-base"><%= t("card.temperature") %></div>
<div class="stat-value text-3xl"><%= weather_art.temperature %>°C</div>
<div class="stat-desc">Feels like <%= weather_art.feeling_temp %>°C</div>
<div class="stat-desc"><%= t("card.feel_like") %> <%= weather_art.feeling_temp %>°C</div>
</div>
<div class="stat bg-gradient-to-br from-secondary/10 to-secondary/20 hover:from-secondary hover:to-secondary/30 p-4 rounded-lg">
<div class="stat-title font-medium text-base">Wind</div>
<div class="stat-title font-medium text-base"><%= t("card.wind") %></div>
<div class="stat-value text-3xl"><%= weather_art.wind_scale %></div>
<div class="stat-desc"><%= weather_art.wind_speed %> km/h</div>
</div>
<div class="stat bg-base-300 hover:bg-base-400 p-4 rounded-lg">
<div class="stat-title font-medium text-base">Humidity</div>
<div class="stat-title font-medium text-base"><%= t("card.humidity") %></div>
<div class="stat-value text-3xl"><%= weather_art.humidity %>%</div>
<div class="stat-desc">Relative humidity</div>
<div class="stat-desc"><%= t("card.relative_humidity") %></div>
</div>
<div class="stat bg-base-300 hover:bg-base-400 p-4 rounded-lg">
<div class="stat-title font-medium text-base">Visibility</div>
<div class="stat-title font-medium text-base"><%= t("card.visibility") %></div>
<div class="stat-value text-3xl"><%= weather_art.visibility %> km</div>
<div class="stat-desc">Clear view distance</div>
<div class="stat-desc"><%= t("card.clear_view_distance") %></div>
</div>
<div class="stat bg-accent/10 hover:bg-accent p-4 rounded-lg">
<div class="stat-title font-medium text-base">Pressure</div>
<div class="stat-title font-medium text-base"><%= t("card.pressure") %></div>
<div class="stat-value text-3xl"><%= weather_art.pressure %> hPa</div>
<div class="stat-desc">Atmospheric pressure</div>
<div class="stat-desc"><%= t("card.atmospheric_pressure") %></div>
</div>
<div class="stat bg-base-200 hover:bg-base-100 p-4 rounded-lg">
<div class="stat-title font-medium text-base">Cloud Cover</div>
<div class="stat-title font-medium text-base"><%= t("card.cloud_cover") %></div>
<div class="stat-value text-3xl"><%= weather_art.cloud %>%</div>
<div class="stat-desc">Sky coverage</div>
<div class="stat-desc"><%= t("card.sky_coverage") %></div>
</div>

View File

@ -4,105 +4,111 @@
</script>
<% end %>
<div class="relative min-h-screen bg-white"> <!-- 使用更明快的背景颜色 -->
<div class="container mx-auto px-4 pt-12 pb-16">
<div class="max-w-6xl mx-auto space-y-6">
<!-- 返回导航 -->
<div class="flex items-center">
<%= link_to city_path(@weather_art.city),
class: "btn btn-ghost btn-md gap-2 bg-base-200 hover:bg-base-300 transition-all duration-300" do %>
<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" />
</svg>
Back to <%= @weather_art.city.name %>
<% end %>
<div class="min-h-screen bg-base-100">
<div class="container mx-auto px-4 md:px-6 pt-8 pb-16">
<!-- 返回按钮 -->
<%= link_to city_path(@weather_art.city),
class: "btn btn-ghost btn-md gap-2 bg-base-200 hover:bg-base-300 transition-all duration-300 mb-4" do %>
<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" />
</svg>
<%= "#{t("button.back_to")} #{@weather_art.city.name}" %>
<% end %>
<!-- 标题区域 -->
<div class="max-w-6xl mx-auto mb-8">
<h1 class="font-display text-4xl md:text-5xl font-bold text-gradient mb-4">
<%= @weather_art.city.full_name %> Weather Art
</h1>
<div class="flex flex-wrap gap-4 mb-6">
<div class="badge badge-lg badge-primary">
<%= "#{@weather_art&.city&.country&.emoji + " " || ""}#{@city&.country&.localized_name}" %>
</div>
<div class="badge badge-lg badge-secondary">
<%= @weather_art&.city&.state&.name %>
</div>
<div class="badge badge-lg badge-ghost">
<%= @weather_art.formatted_time(:date) %>
</div>
<div class="badge badge-lg badge-ghost">
<%= @weather_art.formatted_time(:time, true) %>
</div>
</div>
</div>
<!-- 主要内容 -->
<div class="card bg-base-100 backdrop-blur-md shadow-lg border border-primary/20 overflow-hidden"> <!-- 调整透明度和阴影 -->
<div class="grid lg:grid-cols-2 gap-6 items-center">
<!-- 图片区域 -->
<% if @weather_art.image.attached? %>
<figure class="relative lg:h-[30rem] h-auto overflow-hidden rounded-lg"> <!-- 添加圆角 -->
<div class="gallery" data-controller="photo-swipe-lightbox">
<div data-photo-swipe-lightbox-target="gallery" class="h-full">
<% watermarked = @weather_art.webp_image.processed %>
<%= link_to rails_blob_path(watermarked),
data: {
pswp_src: rails_blob_url(watermarked),
pswp_caption: 'Weather Art',
pswp_width: 1792,
pswp_height: 1024
} do %>
<%= image_tag @weather_art.preview_image(:big).processed, class: "object-cover w-full h-full transition-transform transform hover:scale-105 ease-in-out" %>
<%#= image_tag @weather_art.watermarked_variant.processed , class: "object-cover w-full h-full transition-transform transform hover:scale-105 ease-in-out" %>
<% end %>
</div>
</div>
</figure>
<% end %>
<!-- 信息区域 -->
<div class="card-body p-8 lg:py-10 lg:px-12">
<div class="prose max-w-none">
<h1 class="font-display text-4xl md:text-5xl font-bold text-gradient mb-6">
<%= @weather_art.city.full_name %> Weather Art
</h1>
<div class="flex flex-wrap gap-4 mb-6">
<div class="badge badge-lg badge-primary">
<%= @weather_art.formatted_time(:date) %>
</div>
<div class="badge badge-lg badge-secondary">
<%= @weather_art.formatted_time(:time, true) %>
</div>
</div>
<h2 class="text-2xl font-semibold mb-4">
<%= weather_description_icon(@weather_art.description) %>
<%= @weather_art.description %>
</h2>
<div class="divider"></div>
<div class="grid grid-cols-2 gap-4">
<%= render 'weather_stats', weather_art: @weather_art %> <!-- 使用局部渲染 -->
</div>
<!-- 主要内容区域 -->
<div class="max-w-7xl mx-auto">
<!-- 艺术图像区域 - 移动端全宽显示 -->
<% if @weather_art.image.attached? %>
<div class="md:rounded-xl overflow-hidden mb-8 -mx-4 md:mx-0"> <!-- 负margin实现移动端全宽 -->
<div class="gallery" data-controller="photo-swipe-lightbox">
<div data-photo-swipe-lightbox-target="gallery">
<% watermarked = @weather_art.webp_image.processed %>
<%= link_to rails_blob_path(watermarked),
data: {
pswp_src: rails_blob_url(watermarked),
pswp_caption: 'Weather Art',
pswp_width: 1792,
pswp_height: 1024
} do %>
<%= image_tag @weather_art.preview_image(:big).processed,
class: "w-full h-auto object-cover transition-transform hover:scale-105 duration-300 ease-in-out" %>
<% end %>
</div>
</div>
</div>
<% end %>
<!-- 天气信息卡片 -->
<div class="grid md:grid-cols-2 gap-6 mb-8">
<div class="card bg-base-100 shadow-lg">
<div class="card-body">
<h2 class="text-2xl font-semibold mb-4 flex items-center gap-2">
<%= weather_description_icon(@weather_art.description) %>
<%= @weather_art.description %>
</h2>
<div class="divider"></div>
<div class="grid grid-cols-2 gap-4">
<%= render 'weather_stats', weather_art: @weather_art %>
</div>
</div>
</div>
<!-- AI Prompt 卡片 -->
<div class="card bg-primary/10 shadow-lg">
<div class="card-body">
<div class="flex items-center gap-3 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<h3 class="font-display font-bold text-lg"><%= t("title.ai_prompt") %></h3>
</div>
<p class="text-base-content/80 leading-relaxed">
<%= @weather_art.prompt %>
</p>
</div>
</div>
</div>
<div class="card bg-base-100 backdrop-blur-md shadow-lg border border-primary/20 overflow-hidden"> <!-- 调整透明度和阴影 -->
<!-- 地图区域 -->
<div class="card bg-base-100 shadow-lg mb-8">
<%= render 'shared/map', city: @weather_art.city %>
</div>
<div class="card bg-base-100 backdrop-blur-md shadow-lg overflow-hidden">
<!-- 广告区域 -->
<div class="card bg-base-100 shadow-lg mb-8">
<%= render 'shared/auto_ad' %>
</div>
<!-- AI Prompt -->
<div class="bg-primary/10 backdrop-blur-md p-6 rounded-lg border border-primary/20">
<div class="flex items-center gap-3 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<h3 class="font-display font-bold text-lg">AI Prompt</h3>
</div>
<p class="text-base-content/80 leading-relaxed">
<%= @weather_art.prompt %>
</p>
</div>
<!-- 社交分享 -->
<%
# 构建更吸引人的分享标题
share_title = [
"🎨 Amazing AI Weather Art: #{@weather_art.city.full_name}",
"#{@weather_art.description} at #{@weather_art.temperature}°C",
"#{@weather_art.formatted_time(:all, true)}"
].join("\n")
# 构建更有描述性的分享描述
share_description = [
"Discover this stunning AI-generated weather art!",
"#{@weather_art.description} in #{@weather_art.city.full_name}.",
@ -116,8 +122,6 @@
tags: "AIWeather,Art,AIart,Weather,#{@weather_art.city&.name},#{@weather_art&.city&.country&.name}",
image: url_for(@weather_art.webp_image.processed)
%>
</div>
</div>
</div>
</div>
</div>

View File

@ -5,9 +5,28 @@ class BatchGenerateWeatherArtsWorker
MAX_DURATION = 5.minutes
SLEEP_DURATION = 10.seconds
DAILY_GENERATION_LIMIT = 60 # 每日生成图片上限
PER_RUN_GENERATION_LIMIT = 3 # 每次运行生成图片上限
PER_RUN_GENERATION_LIMIT = 2 # 每次运行生成图片上限
def perform(*args)
lock_key = "batch_generate_weather_lock"
lock_ttl = 300 # 锁的生存时间,单位为秒
redis = Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/1"))
if redis.set(lock_key, Time.current.to_s, nx: true, ex: lock_ttl)
begin
batch_tasks
ensure
redis.del(lock_key)
end
else
Rails.logger.info "Sitemap refresh is already in progress"
end
end
private
def batch_tasks
start_time = Time.current
remaining_slots = calculate_remaining_slots
return if remaining_slots <= 0
@ -38,8 +57,6 @@ class BatchGenerateWeatherArtsWorker
print_summary(processed_cities, skipped_cities)
end
private
def get_recent_cities
cutoff_time = Time.current - GENERATION_INTERVAL
City.joins("LEFT JOIN (

View File

@ -10,7 +10,14 @@ class RefreshSitemapWorker
if redis.set(lock_key, Time.current.to_s, nx: true, ex: lock_ttl)
begin
generate_sitemap
setup_sitemap_config
# 生成默认的不带语言前缀的 sitemap
generate_sitemap(nil)
# 为每个可用语言生成带前缀的 sitemap
I18n.available_locales.each do |locale|
generate_sitemap(locale)
end
ensure
redis.del(lock_key)
end
@ -21,58 +28,102 @@ class RefreshSitemapWorker
private
def generate_sitemap
host = Rails.env.production? ? "https://todayaiweather.com" : "http://127.0.0.1:3000"
Rails.application.routes.default_url_options[:host] = host
SitemapGenerator::Sitemap.default_host = ENV.fetch("RAILS_SITEMAP_DEFAULT_HOST", host)
def setup_sitemap_config
@host = Rails.env.production? ? "https://todayaiweather.com" : "http://127.0.0.1:3000"
Rails.application.routes.default_url_options[:host] = @host
SitemapGenerator::Sitemap.default_host = ENV.fetch("RAILS_SITEMAP_DEFAULT_HOST", @host)
if Rails.env.production?
SitemapGenerator::Sitemap.adapter = SitemapGenerator::AwsSdkAdapter.new(
ENV.fetch("AWS_BUCKET", Rails.application.credentials.dig(:aws, :bucket)),
aws_access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws, :access_key_id)),
aws_secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws, :secret_access_key)),
aws_region: ENV.fetch("AWS_REGION", "wnam"),
endpoint: ENV.fetch("AWS_ENDPOINT", Rails.application.credentials.dig(:aws, :endpoint)),
ENV.fetch("AWS_BUCKET", Rails.application.credentials.dig(:minio, :bucket)),
aws_access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio, :access_key_id)),
aws_secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio, :secret_access_key)),
aws_region: ENV.fetch("AWS_REGION", Rails.application.credentials.dig(:minio, :region)),
force_path_style: ENV.fetch("AWS_FORCE_PATH_STYLE", Rails.application.credentials.dig(:minio, :force_path_style)),
endpoint: ENV.fetch("AWS_ENDPOINT", Rails.application.credentials.dig(:minio, :endpoint)),
)
else
SitemapGenerator::Sitemap.adapter = SitemapGenerator::AwsSdkAdapter.new(
ENV.fetch("AWS_DEV_BUCKET", Rails.application.credentials.dig(:aws_dev, :bucket)),
aws_access_key_id: ENV.fetch("AWS_DEV_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws_dev, :access_key_id)),
aws_secret_access_key: ENV.fetch("AWS_DEV_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws_dev, :secret_access_key)),
aws_region: ENV.fetch("AWS_DEV_REGION", "wnam"),
endpoint: ENV.fetch("AWS_DEV_ENDPOINT", Rails.application.credentials.dig(:aws_dev, :endpoint)),
ENV.fetch("AWS_DEV_BUCKET", Rails.application.credentials.dig(:minio_dev, :bucket)),
aws_access_key_id: ENV.fetch("AWS_DEV_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio_dev, :access_key_id)),
aws_secret_access_key: ENV.fetch("AWS_DEV_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio_dev, :secret_access_key)),
aws_region: ENV.fetch("AWS_DEV_REGION", Rails.application.credentials.dig(:minio_dev, :region)),
force_path_style: ENV.fetch("AWS_FORCE_PATH_STYLE", Rails.application.credentials.dig(:minio_dev, :force_path_style)),
endpoint: ENV.fetch("AWS_DEV_ENDPOINT", Rails.application.credentials.dig(:minio_dev, :endpoint)),
)
end
SitemapGenerator::Sitemap.sitemaps_path = "sitemaps/"
end
SitemapGenerator::Sitemap.create do
add root_path, changefreq: "daily", priority: 1.0
add cities_path, changefreq: "daily", priority: 0.9
add arts_path, changefreq: "daily", priority: 0.9
def generate_sitemap(locale = nil)
# 设置当前语言环境
I18n.locale = locale || I18n.default_locale
Rails.application.routes.default_url_options[:locale] = locale
# 设置 sitemap 路径
# path_prefix = locale ? "sitemaps/#{locale}/" : "sitemaps/"
# SitemapGenerator::Sitemap.sitemaps_path = path_prefix
filename = locale==nil ? "sitemap" : "sitemap_#{locale}"
SitemapGenerator::Sitemap.create(filename: filename) do
available_locales = I18n.available_locales
# 首页
add root_path(locale: locale),
changefreq: "daily",
priority: 1.0,
alternate: available_locales.map { |al|
{ lang: al, href: root_url(locale: al) }
}
# 城市列表页
add cities_path(locale: locale),
changefreq: "daily",
priority: 0.9,
alternate: available_locales.map { |al|
{ lang: al, href: cities_url(locale: al) }
}
# 艺术作品列表页
add arts_path(locale: locale),
changefreq: "daily",
priority: 0.9,
alternate: available_locales.map { |al|
{ lang: al, href: arts_url(locale: al) }
}
# 城市详情页
City.find_each do |city|
add city_path(city),
add city_path(city, locale: locale),
changefreq: "daily",
priority: 0.8,
lastmod: city.updated_at
lastmod: city.updated_at,
alternate: available_locales.map { |al|
{ lang: al, href: city_url(city, locale: al) }
}
end
# 天气艺术作品页
WeatherArt.includes(:city).find_each do |art|
if art.image.attached?
add city_weather_art_path(art.city, art),
add city_weather_art_path(art.city, art, locale: locale),
changefreq: "daily",
priority: 0.7,
lastmod: art.updated_at,
images: [ {
loc: url_for(art.image),
title: "#{art.city.name} Weather Art - #{art.weather_date.strftime('%B %d, %Y')}"
} ]
loc: url_for(art.image),
title: "#{art.city.name} Weather Art - #{art.weather_date.strftime('%B %d, %Y')}"
} ],
alternate: available_locales.map { |al|
{ lang: al, href: city_weather_art_url(art.city, art, locale: al) }
}
end
end
end
# SitemapGenerator::Sitemap.ping_search_engines if Rails.env.production?
Rails.logger.info "Sitemap has been generated and uploaded to S3 successfully"
Rails.logger.info "Generated sitemap for #{locale || 'default'} version"
rescue => e
Rails.logger.error "Error refreshing sitemap: #{e.message}"
Rails.logger.error "Error generating sitemap for #{locale || 'default'}: #{e.message}"
raise e
end
end

View File

@ -1 +1 @@
VUzo0X7975miXMnnoTVXWA5260MuOUxsdsVtC079F3Tquo6Z8Re/EBiIM4hUKHX91D5CsVmPquvwj/AxW9JQxKtSUWYepyildV1B7nYAceqefvrqU+98qYvcKFbwTYXmw4kZD8lOgElcGEUNq6LgCHHXfrtEDHk0Sl6k8Pf5Pgc/SheFZwtK9FE4MGQ6y+hNaeMb7ct2E1zKKqyJKB4Lzp/Fgo7VJuIdEy8Ck23UweaJbSJ08yfS7XrmG1i2ak3fH8FCDxUrofIvyzLw27g8th071JhqiiS9fxJezH+JdaACnKao3bi5fSDL37R0nSBLOYV/Pm63Q3/Nmr560neNgErYtbIZPLETqAZTIK21yvrdaQAXlBEy6St8gdxpQNPELIVJAZ0fAaTfoFdS4/WlMt9oz9Osey9e+eWmDFhpQ/lAj+5oU0t81ioO4S+DBob6rgsAltGK/wa3GdXO2j7ACVJBv0znX3Mran8QiqGYl85+UNxLDH8fIkDyKiug8naWSxQYZIrFOHY5DG4M/IrI5u0zKiks+6kRDN7pRm1WDuW6EpEG5fDv4JRaRp41vS6ezCzXYDPIrGMDtksoJp64dDXdtD6w8NxSSXVhdBzcpZWDXzjpo/rmBM1t8WM46FsAKBrQNTi26B7q1J3rXb3zw2WmzzvVvEuKqVpKbc+d8ND8XUTBYM4d4bPrVSUtC0twMPwAnR0P3PNmV7EHCgfnOuKqDgdqpREpePPzHRpXTS1AxSehLRLO7te8SPN+NpD8Yz+drGmoV1Tx7MuymzK45ILRoBgKaBFkCVZayxhGxDRPDjtYLkQ3ZTA3MUwRhyDVQX47J3wAL5zVRmPiWobXoWEtFalW61bxvD9n9Mr1WP59oYlU4xBxb+0Yu+mvysLBMW9PIAviUknKzmkjrUu4ywqV2QrWlT2tZtM/gWDt5tvLWKDPB89SlzZ55Y6auGFFbYv7HUICo3lk2JFXNfebJF0oXEcmYq3szb80IVIWgp4pdNXOYyh9Pu7g7dZnGTPyiITmhHge/9mj7FoOxOGDsvMedefN86IPN/wCEg/fsJBzhJ0YovrwaTn5+GN9NV92TqwK3LWGkQZ4aZnOAJxIO6b4E3cebPfYjnps39pI0sx9aIN5b0XbjI6d50iLh5jz+2v0B6nDzVEyCiG6zpXXi27ZehO921G0dEQrCuWzVikquUnJbJGVf2tdwKjRDCJWhwPIkhP9YeY6uzQTjGtqDrd12VoEReFMgn1K/PjpJ0s55slbc9+BjOCCuoDTXDy6NC4n3/bsNNKPjt5x0pXkTDh90ug7IBdLweXskhrR86FBwZuf8GGZfz4ZbrVlzhIIPiG/UbkzhaVJDcJrawHj2ipcToXIoDg4Wt3+ZTGC+Kjxk5DzLJWxtIReU34xU9Fv+jndWnu4GJ4+scbwS3mUhBXdcMufeRVh1T45/PSnkwLJRVMv5c5f3CP16PkDORrmuIC+ws3Dz5lCEu1/tuaFabtY3sF6jUY6h/EIUxMllrGwzH9PI/dLu3UizRdtBR85iW03wvAxmLAABmXuRhgyca50uJ65qnFhzLRtQ3JlAIRxZT62YkawqAlUBgstuUgbMLxsCJXLhMmBCO+GuqvkF9DkPVO5dVkNj9V/dnuzqhwzbkDHGd5hwMrXMIYVii5e4q+D5eiTuSfGo/kdHIXfrGrUpUkmbkRrTsnNDUnDzDCk5CWUpuiIj7FSUY8L8Yokli04HRCCn++niPVHZM/RZIZQXAqt61YpfgPr80ut3tmW1aRsLMnkOTjL0KTW3xDv/hxjKuTZj3JqKOFISdI5zihYRwHDBTiClWEnGqkuqSS9fl56A/m17yviId15rzvBIdcLRziQnnpgUacX0lcMdv/A48SCJwAKhTzc2f9Jt8IbFW2OH3+o6QPQC3mysl9ESatJ0saZ7VFQvGSRqG+iRY7OlX2d4LssBke0KBkU4EnZOPXQ24A72m1nooWiz83DBe62d9A/XoKkX9OFh0XQypjTTu0KHqdLsEAqFqISyCz7Vxjd3pdVjaC1lJ/4hqFn3M8RJNTRefXNzgkG1ZpEOA7IGsal+rUaEDSXC8by--JNyAqrs+Fsmpu043--vwHHQzq3PsM/qcTSemHHAg==
geGCObHW9aRt27AEzTpe7YfR9YNZtC1NVigIGs72aWNrwAkXB1DnXeDZH4OMJTPug+nxO4/5trV/XgwpTHPyqY4t1cbB22oX2aYSUijnJKz4rWaVi8K+OQfInc1pQSro9yTkJrT76a1UCYR27yxhR+l/+CxVb8yNfHMhPOLXpjhJKE6D2bppxkFJ7MTQ6FEAMxtgLLL7aDIMkt+KLhep/dfeKhW0IbZsng7MA5ExL8sd+eM3twRgTr9XIThpJ8v6lgo69fsINvaUxYdgZddqOQ+O7EeYaBGasE/RCR2DULreKwEWLA7fLgPuqrxs4EqGxdiD9LeNKZN40sJw360ZbmLtRUsMn7hEbFRcUfito7aatJ97Q/TStvTkhyWqLgt4JsqlRXHtbFayUiixwQI9WJY0iF1by6lBtoXiA8HsifY2t7muv4OaMI2386LptZ3JiIFaQOfaFJRPHmMSfejj13TaPs6J6gDDvpeLv95lhpwbMu79+zdYGeEtFK71kx2rj266YGhe5dKttcMlcNWke1WwS+GIFArjEO5sR057Z//Q/fP+voW+GL/aVgt4qySnX0Fd9kGh0ZkEtSufP8djFRYM/Yl7NjzM4ofoww3ghtzT0+eahktfpEZMqqBdLtxKf4tQSKO5xMAW2puRThunKiQeLrcuDh0PVkVeDbSwH05Wq0GFHwNft3d62Bg9310PIGHGkKU8eWnLfgn3dNy8HsihXfAgZHLzZSDZfy6htqXYsj9Vk4LeV6c359dI2lPuxb8ka0c1jjnKniPiYpcHqSUd46lPYuKEdP49ezxdOLzrkGRpiGiX4iqIV0bUrB1GLvJ2N2kcblkprvoR4daNqCuLl8noUfEczDl+puDOZg79XMWvp1zJDVyHyPnCG58951VI6GpvNg1LpF7e+EuUIBbyjBXbiyisLCujYy2Xi4RGSF+z++FVRUdXDHHATPcFoGo4kOcPiAncde7D/vM3HlaU+SmFA8vSV1BMVsKBvHyuI0SEyD3JxgCz4/Bv9cMjF4Xhtf8xcz3746jZDnUWWk/wDjUNjCm/btI3TEsQ5e2InPohL2qG/86RPhyRvGFPdDyS1ulscSNrEDZd1yuPQ3ZZaXaHpUD4K/M/nWdWvNbsrQ9J/goob+x3VwqtASdf6a4zIdGlgNhOdsYogSqms/GlKaih1jMCKUXsnY5s5phd87Tw5Gtq6I1QKOa0p2Y6grIr0uOvU/DnxlSvKbm5/tpkF7v05hOCdDRwu4//MYGQPXXCOBF3icgQvmlwBZ7kEWFLmpOFheZ4koqMyxK9GrgQnKwBLpsu0lZG4p8wNSboj5gR9MABVxTYlMVkfIPuNXhU5iWH9g2KpagDgyPcN4PHsJgBXWBBmcUvdO4DMWfH9Z7z6Q1V3TImpUabHtRN5dsL9iAepMlt4xrlqUBdWCjhBYKpX5zYukkCy83ccaKB448KupgnjbW12MFy1pLTLEKhtLV8woX3QAFDQJgFfoH8fD4lx3C25lLvfJPYSlp2xFAcNy+B3mdUF306Iff6XmOCzKlSGI39HWjYA3jpv9031T0YwY6eVKGbQyZMzO7QVeK2lSn+0BA+55INJ5hdP6XlwRlS4lidbPIxUAbMpC3yILMjImc36FvHdomuAAGC1s5naFCFtsl8LpVcdlYsX7Jwf56TV7whchV9+45aStNANyfSH6dhkikquA7i8DBPWnSbgHyomu8ihb4qVSqvtCJcw6Oh7oSz9zrQsNZkmZ+PmsGx37yY80ZZNUbdqqpIuNzTrEU5jgHbCpJqGGbEjjysleB6A3cnVLbBXWHlQubZY/NdpRMCsuThyf4YEhez6OTxd+GEwiCuJse7dSHw3N0xfkFaqad0fR+BJAXIItqIAgYMI/AMyyqrF9lIYk5YTTUSML4Ae0wIEo6tteNNxly/Ofz+DFF/GOCkBxQ88yLBGAq2QG/wkQ9upBRBAZ+dFHeazh9fi1W811EjpDEiqELEqNbxgbV+2cWBbbwVb0917+SVEWDgxer0O+iZUyYjJ9Q8rTtikjWdGuikikz8HnMY6v6kl7TC4bP3gPsyxiLk1JnmTJYGtRrJXQYH3ImlHJKVaS0gtCXZ/rXBcbqGZXpEW4b4ZeVQalMFtQW5BT+rqW7h8f+FkEeV6xKaauOsnPg3fqVfCazRMkuvKatdkn/rF/V2RjydsK3rLfAvLwfz89XRpekzI9PGJJsUKqEFO/VSoEKXedqn/tQkdGWok4VqYoplDWarCbi2sQJQztbwTUCcBT87m+a/oxHyqCrXi5hTpomCHXPrTovilF28s12Zl8ivWpFTebjtxvD/QarAjrWgx42KUuISEAAuUoVxga+ICxd0WoZlSrhcwAntWWvdQkzW41t9IUvzlQX0O9AdZEki81lRRASzEMS28ge7pFv7VkLMIplzY0Ltl/GOq1q+flX1zebEzjQjcq0PiXoIWLAj8PNIhHV/aFwH+jAwcH4pYpO8aQNjhoYBM9KZ1W5ZWSphehdrIddZ5oiZsUs9PMwFza28bAdQvCVarcrF2ofOZCNTLOdL6FntoUPovxemuQERnZ0V5xvVYnLMW8KnHjLYJqPmVMdd38gZe1Qf4JeOz+2WyW/E0SSz2dOt0zKp3klyURwr5+dlTQeGy7D1a4OPxvRIwAAr3cGNpPG2NBzGNeDSargSDrQIRBpp23gaGQpJrfnR95MFvNSx9IG9GkK+ifpwAbtsqEjF2iTYJ9BKSR8VIODLx/VoUD/1euTI--w1b7CHBZ4zFPRjgq--DpdTPDeqnUeN8aZTrINdZg==

View File

@ -1,19 +1,21 @@
if Rails.env.production?
Aws.config.update({
region: ENV.fetch("AWS_REGION", "wnam"),
region: ENV.fetch("AWS_REGION", Rails.application.credentials.dig(:minio, :region)),
credentials: Aws::Credentials.new(
ENV.fetch("AWS_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws, :access_key_id)),
ENV.fetch("AWS_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws, :secret_access_key))
ENV.fetch("AWS_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio, :access_key_id)),
ENV.fetch("AWS_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio, :secret_access_key))
),
endpoint: ENV.fetch("AWS_ENDPOINT", Rails.application.credentials.dig(:aws, :endpoint))
force_path_style: ENV.fetch("AWS_FORCE_PATH_STYLE", Rails.application.credentials.dig(:minio_dev, :force_path_style)),
endpoint: ENV.fetch("AWS_ENDPOINT", Rails.application.credentials.dig(:minio, :endpoint))
})
else
Aws.config.update({
region: ENV.fetch("AWS_DEV_REGION", "wnam"),
region: ENV.fetch("AWS_DEV_REGION", Rails.application.credentials.dig(:minio_dev, :region)),
credentials: Aws::Credentials.new(
ENV.fetch("AWS_DEV_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws_dev, :access_key_id)),
ENV.fetch("AWS_DEV_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws_dev, :secret_access_key))
ENV.fetch("AWS_DEV_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio_dev, :access_key_id)),
ENV.fetch("AWS_DEV_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio_dev, :secret_access_key))
),
endpoint: ENV.fetch("AWS_DEV_ENDPOINT", Rails.application.credentials.dig(:aws_dev, :endpoint))
force_path_style: ENV.fetch("AWS_DEV_FORCE_PATH_STYLE", Rails.application.credentials.dig(:minio_dev, :force_path_style)),
endpoint: ENV.fetch("AWS_DEV_ENDPOINT", Rails.application.credentials.dig(:minio_dev, :endpoint))
})
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", :ja, :ko, :"pt-BR", :hr, :fa, :de, :es, :fr, :it, :tr, :ru, :uk, :pl, :bn, :hi, :ur, :ar ]
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

67
config/locales/ar.yml Normal file
View File

@ -0,0 +1,67 @@
ar:
hello: "مرحباً بالعالم"
brand:
name: "الطقس اليوم بالذكاء الاصطناعي"
title:
cities: "المدن"
arts: "الفنون"
sign_in: "تسجيل الدخول"
sign_out: "تسجيل الخروج"
settings: "الإعدادات"
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:
title: "معرض فنون الطقس"
subtitle: "اكتشف فن الطقس المُنشأ بالذكاء الاصطناعي من مدن حول العالم"
home:
headline_html: حيث يلتقي الطقس<br>بالذكاء الاصطناعي
subtitle:
اختبر الطقس من خلال عدسة الفن المُنشأ بالذكاء الاصطناعي،
مما يجلب منظوراً جديداً للظواهر الجوية اليومية.
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: "عرض %{from} إلى %{to} من %{total} %{items}"
items:
weather: "سجلات الطقس"
default: "العناصر"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%B %d, %Y"

67
config/locales/bn.yml Normal file
View File

@ -0,0 +1,67 @@
bn:
hello: "ওহে বিশ্ব"
brand:
name: "টুডে এআই ওয়েদার"
title:
cities: "শহরগুলি"
arts: "শিল্প"
sign_in: "সাইন ইন"
sign_out: "সাইন আউট"
settings: "সেটিংস"
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:
title: "আবহাওয়া শিল্প গ্যালারি"
subtitle: "বিশ্বজুড়ে শহরগুলি থেকে এআই-জেনারেটেড আবহাওয়া শিল্প আবিষ্কার করুন"
home:
headline_html: যেখানে আবহাওয়া মিলিত হয়<br>কৃত্রিম বুদ্ধিমত্তার সাথে
subtitle:
এআই-জেনারেটেড শিল্পের মাধ্যমে আবহাওয়া অনুভব করুন,
দৈনিক আবহাওয়া ঘটনার একটি নতুন দৃষ্টিভঙ্গি আনয়ন করে।
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}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%B %d, %Y"

View File

@ -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'

View File

@ -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: '澳大利亚'

67
config/locales/de.yml Normal file
View File

@ -0,0 +1,67 @@
de:
hello: "Hallo Welt"
brand:
name: "Today AI Weather"
title:
cities: "Städte"
arts: "Kunst"
sign_in: "Anmelden"
sign_out: "Abmelden"
settings: "Einstellungen"
admin_dashboard: "Admin-Dashboard"
latest_weather_art: "Neueste Wetterkunst"
popular_weather_art: "Beliebte Wetterkunst"
ai_prompt: "KI-Prompt"
text:
latest_from: "Neuestes von"
search_cities: "Städte suchen..."
all_regions: "Alle Regionen"
all_countries: "Alle Länder"
all_in: "Alles in"
showing: "Zeigt"
weather_arts: "Wetterkunst"
newest_first: "Neueste zuerst"
oldest_first: "Älteste zuerst"
cities:
title: "Städte erkunden"
arts:
title: "Wetterkunst-Galerie"
subtitle: "Entdecken Sie KI-generierte Wetterkunst aus Städten auf der ganzen Welt"
home:
headline_html: Wo Wetter auf<br>Künstliche Intelligenz trifft
subtitle:
Erleben Sie Wetter durch die Linse KI-generierter Kunst,
die eine neue Perspektive auf tägliche meteorologische Phänomene bietet.
button:
explore_cities: "Städte erkunden"
view_detail: "Details anzeigen"
view_all_weather_arts: "Alle Wetterkunst anzeigen"
back_to_cities: "Zurück zu Städten"
back_to: "Zurück zu"
card:
temperature: "Temperatur"
wind: "Wind"
humidity: "Luftfeuchtigkeit"
visibility: "Sichtweite"
pressure: "Luftdruck"
cloud_cover: "Bewölkung"
feel_like: "Gefühlt wie"
relative_humidity: "Relative Luftfeuchtigkeit"
clear_view_distance: "Klare Sichtweite"
atmospheric_pressure: "Atmosphärischer Druck"
sky_coverage: "Himmelsbedeckung"
pagination:
showing_items: "Zeigt %{from} bis %{to} von %{total} %{items}"
items:
weather: "Wetteraufzeichnungen"
default: "Einträge"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%d. %b"
long: "%d. %B %Y"

View File

@ -28,4 +28,90 @@
# enabled: "ON"
en:
hello: "Hello world"
language:
en: "English"
zh-CN: "简体中文"
ja: "日本語"
ko: "한국어" # 韩语
pt-BR: "Português (Brasil)" # 巴西葡萄牙语
pt: "Português" # 葡萄牙语
hr: "Hrvatski" # 克罗地亚语
fa: "فارسی" # 波斯语(法尔西语)
de: "Deutsch" # 德语
es: "Español" # 西班牙语
fr: "Français" # 法语
it: "Italiano" # 意大利语
tr: "Türkçe" # 土耳其语
ru: "Русский" # 俄语
uk: "Українська" # 乌克兰语
pl: "Polski" # 波兰语
bn: "বাংলা" # 孟加拉
hi: "हिंदी" # 印地语
ur: " اردو" # 乌尔都语
ar: "العربية" # 阿拉伯
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"
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:
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"
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:
weather: "weather records"
default: "items"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%B %d, %Y"

67
config/locales/es.yml Normal file
View File

@ -0,0 +1,67 @@
es:
hello: "Hola mundo"
brand:
name: "Today AI Weather"
title:
cities: "Ciudades"
arts: "Arte"
sign_in: "Iniciar sesión"
sign_out: "Cerrar sesión"
settings: "Configuración"
admin_dashboard: "Panel de administración"
latest_weather_art: "Último arte del tiempo"
popular_weather_art: "Arte del tiempo popular"
ai_prompt: "Prompt de IA"
text:
latest_from: "Lo último de"
search_cities: "Buscar ciudades..."
all_regions: "Todas las regiones"
all_countries: "Todos los países"
all_in: "Todo en"
showing: "Mostrando"
weather_arts: "Arte del tiempo"
newest_first: "Más recientes primero"
oldest_first: "Más antiguos primero"
cities:
title: "Explorar ciudades"
arts:
title: "Galería de arte del tiempo"
subtitle: "Descubre arte generado por IA del tiempo de ciudades de todo el mundo"
home:
headline_html: Donde el tiempo se encuentra<br>con la Inteligencia Artificial
subtitle:
Experimenta el tiempo a través del lente del arte generado por IA,
brindando una nueva perspectiva a los fenómenos meteorológicos diarios.
button:
explore_cities: "Explorar ciudades"
view_detail: "Ver detalles"
view_all_weather_arts: "Ver todo el arte del tiempo"
back_to_cities: "Volver a ciudades"
back_to: "Volver a"
card:
temperature: "Temperatura"
wind: "Viento"
humidity: "Humedad"
visibility: "Visibilidad"
pressure: "Presión"
cloud_cover: "Cobertura de nubes"
feel_like: "Sensación térmica"
relative_humidity: "Humedad relativa"
clear_view_distance: "Distancia de visibilidad clara"
atmospheric_pressure: "Presión atmosférica"
sky_coverage: "Cobertura del cielo"
pagination:
showing_items: "Mostrando %{from} a %{to} de %{total} %{items}"
items:
weather: "registros meteorológicos"
default: "elementos"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%d de %B de %Y"

67
config/locales/fa.yml Normal file
View File

@ -0,0 +1,67 @@
fa:
hello: "سلام دنیا"
brand:
name: "هوای هوش مصنوعی امروز"
title:
cities: "شهرها"
arts: "هنرها"
sign_in: "ورود"
sign_out: "خروج"
settings: "تنظیمات"
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:
title: "گالری هنرهای آب و هوا"
subtitle: "کشف هنر آب و هوای تولید شده توسط هوش مصنوعی از شهرهای سراسر جهان"
home:
headline_html: جایی که آب و هوا با<br>هوش مصنوعی ملاقات می‌کند
subtitle:
تجربه آب و هوا از طریق لنز هنر تولید شده توسط هوش مصنوعی،
آوردن دیدگاهی جدید به پدیده‌های هواشناسی روزانه.
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: "نمایش %{from} تا %{to} از %{total} %{items}"
items:
weather: "رکوردهای آب و هوا"
default: "موارد"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%B %d, %Y"

67
config/locales/fr.yml Normal file
View File

@ -0,0 +1,67 @@
fr:
hello: "Bonjour le monde"
brand:
name: "Today AI Weather"
title:
cities: "Villes"
arts: "Arts"
sign_in: "Se connecter"
sign_out: "Se déconnecter"
settings: "Paramètres"
admin_dashboard: "Tableau de bord administrateur"
latest_weather_art: "Derniers arts météorologiques"
popular_weather_art: "Arts météorologiques populaires"
ai_prompt: "Prompt IA"
text:
latest_from: "Derniers de"
search_cities: "Rechercher des villes..."
all_regions: "Toutes les régions"
all_countries: "Tous les pays"
all_in: "Tout dans"
showing: "Affichage"
weather_arts: "Arts météorologiques"
newest_first: "Plus récents d'abord"
oldest_first: "Plus anciens d'abord"
cities:
title: "Explorer les villes"
arts:
title: "Galerie d'arts météorologiques"
subtitle: "Découvrez l'art météorologique généré par l'IA des villes du monde entier"
home:
headline_html: "Là où la météo rencontre<br>l'Intelligence Artificielle"
subtitle:
Découvrez la météo à travers le prisme de l'art généré par l'IA,
apportant une nouvelle perspective aux phénomènes météorologiques quotidiens.
button:
explore_cities: "Explorer les villes"
view_detail: "Voir les détails"
view_all_weather_arts: "Voir tous les arts météorologiques"
back_to_cities: "Retour aux villes"
back_to: "Retour à"
card:
temperature: "Température"
wind: "Vent"
humidity: "Humidité"
visibility: "Visibilité"
pressure: "Pression"
cloud_cover: "Couverture nuageuse"
feel_like: "Ressenti"
relative_humidity: "Humidité relative"
clear_view_distance: "Distance de visibilité claire"
atmospheric_pressure: "Pression atmosphérique"
sky_coverage: "Couverture du ciel"
pagination:
showing_items: "Affichage de %{from} à %{to} sur %{total} %{items}"
items:
weather: "relevés météorologiques"
default: "éléments"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%d %B %Y"

67
config/locales/hi.yml Normal file
View File

@ -0,0 +1,67 @@
hi:
hello: "नमस्ते दुनिया"
brand:
name: "टुडे एआई वेदर"
title:
cities: "शहर"
arts: "कला"
sign_in: "साइन इन"
sign_out: "साइन आउट"
settings: "सेटिंग्स"
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:
title: "मौसम कला गैलरी"
subtitle: "दुनिया भर के शहरों से एआई-जनित मौसम कला की खोज करें"
home:
headline_html: जहां मौसम मिलता है<br>कृत्रिम बुद्धिमत्ता
subtitle:
एआई-जनित कला के माध्यम से मौसम का अनुभव करें,
दैनिक मौसम संबंधी घटनाओं को एक नया दृष्टिकोण प्रदान करें।
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}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%B %d, %Y"

67
config/locales/hr.yml Normal file
View File

@ -0,0 +1,67 @@
hr:
hello: "Pozdrav svijete"
brand:
name: "Today AI Weather"
title:
cities: "Gradovi"
arts: "Umjetnost"
sign_in: "Prijava"
sign_out: "Odjava"
settings: "Postavke"
admin_dashboard: "Administratorska ploča"
latest_weather_art: "Najnovija vremenska umjetnost"
popular_weather_art: "Popularna vremenska umjetnost"
ai_prompt: "AI upit"
text:
latest_from: "Najnovije od"
search_cities: "Pretraži gradove..."
all_regions: "Sve regije"
all_countries: "Sve države"
all_in: "Sve u"
showing: "Prikazuje se"
weather_arts: "Vremenska umjetnost"
newest_first: "Najnovije prvo"
oldest_first: "Najstarije prvo"
cities:
title: "Istražite gradove"
arts:
title: "Galerija vremenske umjetnosti"
subtitle: "Otkrijte umjetnost vremena generiranu AI-em iz gradova širom svijeta"
home:
headline_html: Gdje se vrijeme susreće<br>s umjetnom inteligencijom
subtitle:
Doživite vrijeme kroz objektiv umjetnosti generirane AI-em,
donoseći novu perspektivu svakodnevnim meteorološkim pojavama.
button:
explore_cities: "Istražite gradove"
view_detail: "Pogledaj detalje"
view_all_weather_arts: "Pogledaj svu vremensku umjetnost"
back_to_cities: "Natrag na gradove"
back_to: "Natrag na"
card:
temperature: "Temperatura"
wind: "Vjetar"
humidity: "Vlažnost"
visibility: "Vidljivost"
pressure: "Tlak"
cloud_cover: "Naoblaka"
feel_like: "Osjeća se kao"
relative_humidity: "Relativna vlažnost"
clear_view_distance: "Udaljenost čistog pogleda"
atmospheric_pressure: "Atmosferski tlak"
sky_coverage: "Pokrivenost neba"
pagination:
showing_items: "Prikazuje se %{from} do %{to} od %{total} %{items}"
items:
weather: "vremenskih zapisa"
default: "stavki"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%d. %B %Y."

67
config/locales/it.yml Normal file
View File

@ -0,0 +1,67 @@
it:
hello: "Ciao mondo"
brand:
name: "Today AI Weather"
title:
cities: "Città"
arts: "Arte"
sign_in: "Accedi"
sign_out: "Esci"
settings: "Impostazioni"
admin_dashboard: "Dashboard Amministratore"
latest_weather_art: "Ultima Arte Meteorologica"
popular_weather_art: "Arte Meteorologica Popolare"
ai_prompt: "Prompt IA"
text:
latest_from: "Ultimi da"
search_cities: "Cerca città..."
all_regions: "Tutte le Regioni"
all_countries: "Tutti i Paesi"
all_in: "Tutto in"
showing: "Mostrando"
weather_arts: "Arte Meteorologica"
newest_first: "Prima i più Recenti"
oldest_first: "Prima i più Vecchi"
cities:
title: "Esplora Città"
arts:
title: "Galleria Arte Meteorologica"
subtitle: "Scopri l'arte meteorologica generata dall'IA dalle città di tutto il mondo"
home:
headline_html: Dove il Meteo Incontra<br>l'Intelligenza Artificiale
subtitle:
Vivi il meteo attraverso la lente dell'arte generata dall'IA,
portando una nuova prospettiva ai fenomeni meteorologici quotidiani.
button:
explore_cities: "Esplora Città"
view_detail: "Visualizza Dettagli"
view_all_weather_arts: "Visualizza Tutta l'Arte Meteorologica"
back_to_cities: "Torna alle Città"
back_to: "Torna a"
card:
temperature: "Temperatura"
wind: "Vento"
humidity: "Umidità"
visibility: "Visibilità"
pressure: "Pressione"
cloud_cover: "Copertura Nuvolosa"
feel_like: "Percepita"
relative_humidity: "Umidità relativa"
clear_view_distance: "Distanza di visibilità"
atmospheric_pressure: "Pressione atmosferica"
sky_coverage: "Copertura del cielo"
pagination:
showing_items: "Mostrando da %{from} a %{to} di %{total} %{items}"
items:
weather: "registrazioni meteo"
default: "elementi"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%d %B %Y"

66
config/locales/ja.yml Normal file
View File

@ -0,0 +1,66 @@
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: 天気が出会う場所<br>人工知能
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}"
date:
formats:
short: "%Y-%m-%d"
long: "%Y 年 %m 月 %d 日"

66
config/locales/ko.yml Normal file
View File

@ -0,0 +1,66 @@
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: 날씨가 만나는 곳<br>인공지능
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}"
date:
formats:
short: "%Y-%m-%d"
long: "%Y 년 %m 월 %d 일"

67
config/locales/pl.yml Normal file
View File

@ -0,0 +1,67 @@
pl:
hello: "Witaj świecie"
brand:
name: "Today AI Weather"
title:
cities: "Miasta"
arts: "Sztuka"
sign_in: "Zaloguj się"
sign_out: "Wyloguj się"
settings: "Ustawienia"
admin_dashboard: "Panel administratora"
latest_weather_art: "Najnowsza sztuka pogodowa"
popular_weather_art: "Popularna sztuka pogodowa"
ai_prompt: "Prompt AI"
text:
latest_from: "Najnowsze z"
search_cities: "Szukaj miast..."
all_regions: "Wszystkie regiony"
all_countries: "Wszystkie kraje"
all_in: "Wszystko w"
showing: "Wyświetlanie"
weather_arts: "Sztuka pogodowa"
newest_first: "Od najnowszych"
oldest_first: "Od najstarszych"
cities:
title: "Odkryj miasta"
arts:
title: "Galeria sztuki pogodowej"
subtitle: "Odkryj sztukę pogodową generowaną przez AI z miast na całym świecie"
home:
headline_html: Gdzie pogoda spotyka się<br>ze sztuczną inteligencją
subtitle:
Doświadcz pogody przez pryzmat sztuki generowanej przez AI,
wprowadzając nową perspektywę do codziennych zjawisk meteorologicznych.
button:
explore_cities: "Odkryj miasta"
view_detail: "Zobacz szczegóły"
view_all_weather_arts: "Zobacz całą sztukę pogodową"
back_to_cities: "Powrót do miast"
back_to: "Powrót do"
card:
temperature: "Temperatura"
wind: "Wiatr"
humidity: "Wilgotność"
visibility: "Widoczność"
pressure: "Ciśnienie"
cloud_cover: "Zachmurzenie"
feel_like: "Odczuwalna"
relative_humidity: "Wilgotność względna"
clear_view_distance: "Zasięg widoczności"
atmospheric_pressure: "Ciśnienie atmosferyczne"
sky_coverage: "Pokrycie nieba"
pagination:
showing_items: "Wyświetlanie %{from} do %{to} z %{total} %{items}"
items:
weather: "zapisów pogody"
default: "elementów"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%d %B %Y"

67
config/locales/pt-BR.yml Normal file
View File

@ -0,0 +1,67 @@
pt-BR:
hello: "Olá mundo"
brand:
name: "Today AI Weather"
title:
cities: "Cidades"
arts: "Artes"
sign_in: "Entrar"
sign_out: "Sair"
settings: "Configurações"
admin_dashboard: "Painel de Administração"
latest_weather_art: "Arte Meteorológica Mais Recente"
popular_weather_art: "Arte Meteorológica Popular"
ai_prompt: "Prompt de IA"
text:
latest_from: "Mais recente de"
search_cities: "Pesquisar cidades..."
all_regions: "Todas as Regiões"
all_countries: "Todos os Países"
all_in: "Tudo em"
showing: "Mostrando"
weather_arts: "Artes Meteorológicas"
newest_first: "Mais Recentes Primeiro"
oldest_first: "Mais Antigos Primeiro"
cities:
title: "Explorar Cidades"
arts:
title: "Galeria de Artes Meteorológicas"
subtitle: "Descubra arte meteorológica gerada por IA de cidades ao redor do mundo"
home:
headline_html: Onde o Clima Encontra<br>a Inteligência Artificial
subtitle:
Experimente o clima através das lentes da arte gerada por IA,
trazendo uma nova perspectiva para os fenômenos meteorológicos diários.
button:
explore_cities: "Explorar Cidades"
view_detail: "Ver Detalhes"
view_all_weather_arts: "Ver Todas as Artes Meteorológicas"
back_to_cities: "Voltar para Cidades"
back_to: "Voltar para"
card:
temperature: "Temperatura"
wind: "Vento"
humidity: "Umidade"
visibility: "Visibilidade"
pressure: "Pressão"
cloud_cover: "Cobertura de Nuvens"
feel_like: "Sensação térmica"
relative_humidity: "Umidade relativa"
clear_view_distance: "Distância de visão clara"
atmospheric_pressure: "Pressão atmosférica"
sky_coverage: "Cobertura do céu"
pagination:
showing_items: "Mostrando %{from} até %{to} de %{total} %{items}"
items:
weather: "registros meteorológicos"
default: "itens"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%d/%m/%Y"
short: "%d %b"
long: "%d de %B de %Y"

67
config/locales/pt.yml Normal file
View File

@ -0,0 +1,67 @@
pt:
hello: "Olá mundo"
brand:
name: "Today AI Weather"
title:
cities: "Cidades"
arts: "Artes"
sign_in: "Entrar"
sign_out: "Sair"
settings: "Configurações"
admin_dashboard: "Painel de Administração"
latest_weather_art: "Arte Meteorológica Mais Recente"
popular_weather_art: "Arte Meteorológica Popular"
ai_prompt: "Prompt de IA"
text:
latest_from: "Mais recente de"
search_cities: "Pesquisar cidades..."
all_regions: "Todas as Regiões"
all_countries: "Todos os Países"
all_in: "Tudo em"
showing: "Mostrando"
weather_arts: "Artes Meteorológicas"
newest_first: "Mais Recentes Primeiro"
oldest_first: "Mais Antigos Primeiro"
cities:
title: "Explorar Cidades"
arts:
title: "Galeria de Artes Meteorológicas"
subtitle: "Descubra arte meteorológica gerada por IA de cidades ao redor do mundo"
home:
headline_html: Onde o Clima Encontra<br>a Inteligência Artificial
subtitle:
Experimente o clima através das lentes da arte gerada por IA,
trazendo uma nova perspectiva para os fenômenos meteorológicos diários.
button:
explore_cities: "Explorar Cidades"
view_detail: "Ver Detalhes"
view_all_weather_arts: "Ver Todas as Artes Meteorológicas"
back_to_cities: "Voltar para Cidades"
back_to: "Voltar para"
card:
temperature: "Temperatura"
wind: "Vento"
humidity: "Umidade"
visibility: "Visibilidade"
pressure: "Pressão"
cloud_cover: "Cobertura de Nuvens"
feel_like: "Sensação térmica"
relative_humidity: "Umidade relativa"
clear_view_distance: "Distância de visão clara"
atmospheric_pressure: "Pressão atmosférica"
sky_coverage: "Cobertura do céu"
pagination:
showing_items: "Mostrando %{from} a %{to} de %{total} %{items}"
items:
weather: "registros meteorológicos"
default: "itens"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%d de %b"
long: "%d de %B de %Y"

View File

@ -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'

View File

@ -1,15 +0,0 @@
zh-CN:
regions:
AS: '亚洲'
SA: '南亚'
SEA: '东南亚'
EA: '东亚'
ME: '中东'
AF: '非洲'
NA: '北非'
SSA: '撒哈拉以南非洲'
EU: '欧洲'
NAM: '北美洲'
SAM: '南美洲'
CAM: '中美洲'
OC: '大洋洲'

67
config/locales/ru.yml Normal file
View File

@ -0,0 +1,67 @@
ru:
hello: "Привет, мир"
brand:
name: "Today AI Weather"
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: Где погода встречается<br>с искусственным интеллектом
subtitle:
Познакомьтесь с погодой через призму искусства, созданного искусственным интеллектом,
открывая новый взгляд на ежедневные метеорологические явления.
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: "Показано с %{from} по %{to} из %{total} %{items}"
items:
weather: "записей о погоде"
default: "элементов"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%d %B %Y"

67
config/locales/tr.yml Normal file
View File

@ -0,0 +1,67 @@
tr:
hello: "Merhaba dünya"
brand:
name: "Today AI Weather"
title:
cities: "Şehirler"
arts: "Sanat"
sign_in: "Giriş yap"
sign_out: ıkış yap"
settings: "Ayarlar"
admin_dashboard: "Yönetici Paneli"
latest_weather_art: "En Son Hava Durumu Sanatı"
popular_weather_art: "Popüler Hava Durumu Sanatı"
ai_prompt: "AI Komut"
text:
latest_from: "En son"
search_cities: "Şehirleri ara..."
all_regions: "Tüm Bölgeler"
all_countries: "Tüm Ülkeler"
all_in: "Tümü"
showing: "Gösteriliyor"
weather_arts: "Hava Durumu Sanatları"
newest_first: "En Yeni Önce"
oldest_first: "En Eski Önce"
cities:
title: "Şehirleri Keşfet"
arts:
title: "Hava Durumu Sanat Galerisi"
subtitle: "Dünya genelindeki şehirlerden AI tarafından oluşturulan hava durumu sanatını keşfedin"
home:
headline_html: "Hava Durumu<br>Yapay Zeka ile Buluşuyor"
subtitle:
AI tarafından oluşturulan sanat perspektifinden hava durumunu deneyimleyin,
günlük meteorolojik olaylara yeni bir bakış açısı getirin.
button:
explore_cities: "Şehirleri Keşfet"
view_detail: "Detayları Görüntüle"
view_all_weather_arts: "Tüm Hava Durumu Sanatlarını Görüntüle"
back_to_cities: "Şehirlere Geri Dön"
back_to: "Geri Dön"
card:
temperature: "Sıcaklık"
wind: "Rüzgar"
humidity: "Nem"
visibility: "Görüş Mesafesi"
pressure: "Basınç"
cloud_cover: "Bulut Örtüsü"
feel_like: "Hissedilen"
relative_humidity: "Bağıl nem"
clear_view_distance: "Net görüş mesafesi"
atmospheric_pressure: "Atmosfer basıncı"
sky_coverage: "Gökyüzü kapsama"
pagination:
showing_items: "%{total} %{items} içinden %{from} ile %{to} arası gösteriliyor"
items:
weather: "hava durumu kayıtları"
default: "öğe"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%d %b"
long: "%d %B %Y"

67
config/locales/uk.yml Normal file
View File

@ -0,0 +1,67 @@
uk:
hello: "Привіт світ"
brand:
name: "Today AI Weather"
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: "Відкрийте для себе згенероване ШІ погодне мистецтво з міст по всьому світу"
home:
headline_html: Де погода зустрічається<br>зі штучним інтелектом
subtitle:
Відчуйте погоду через призму мистецтва, створеного ШІ,
що дає новий погляд на щоденні метеорологічні явища.
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: "Показано %{from} до %{to} з %{total} %{items}"
items:
weather: "погодних записів"
default: "елементів"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%d %B %Y"

67
config/locales/ur.yml Normal file
View File

@ -0,0 +1,67 @@
ur:
hello: "ہیلو دنیا"
brand:
name: "ٹوڈے اے آئی ویدر"
title:
cities: "شہر"
arts: "فن"
sign_in: "سائن ان"
sign_out: "سائن آؤٹ"
settings: "ترتیبات"
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:
title: "موسمی فن گیلری"
subtitle: "دنیا بھر کے شہروں سے اے آئی سے تیار کردہ موسمی فن دریافت کریں"
home:
headline_html: جہاں موسم<br>مصنوعی ذہانت سے ملتا ہے
subtitle:
اے آئی سے تیار کردہ فن کے ذریعے موسم کا تجربہ کریں،
روزمرہ موسمیاتی مظاہر کو ایک نیا نظریہ فراہم کرتا ہے۔
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: "%{items} کے %{total} میں سے %{from} سے %{to} تک دکھا رہا ہے"
items:
weather: "موسمی ریکارڈز"
default: "آئٹمز"
time:
formats:
time_only: "%H:%M"
with_zone: "%{time} %{zone}"
date_and_time: "%{date} %{time}"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%B %d, %Y"

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

@ -0,0 +1,65 @@
zh-CN:
hello: "你好"
brand:
name: "全球艺术天气"
title:
cities: "城市探索"
arts: "艺术巡览"
sign_in: "用户登录"
sign_out: "退出登录"
settings: "系统设置"
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:
title: "天象画廊"
subtitle: "邂逅寰宇都市AI气象绘卷"
home:
headline_html: 当气象邂逅<br>人工智能之美
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: "显示第 %{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,49 +1,53 @@
require "sidekiq/web"
Rails.application.routes.draw do
devise_for :users
root "home#index"
scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do
devise_for :users
root "home#index"
resources :cities, only: [ :index, :show ] do
resources :weather_arts, path: "weather", only: [ :show ], param: :slug
end
resources :cities do
member do
post :generate_weather_art, param: :slug
resources :cities, only: [ :index, :show ] do
resources :weather_arts, path: "weather", only: [ :show ], param: :slug
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", to: "sitemaps#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
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
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

View File

@ -2,10 +2,10 @@
host = Rails.env.production? ? "https://todayaiweather.com" : "http://127.0.0.1:3000"
Rails.application.routes.default_url_options[:host] = host
SitemapGenerator::Sitemap.adapter = SitemapGenerator::AwsSdkAdapter.new(
Rails.application.credentials.dig(:aws, :bucket),
aws_access_key_id: Rails.application.credentials.dig(:aws, :access_key_id),
aws_secret_access_key: Rails.application.credentials.dig(:aws, :secret_access_key),
aws_region: Rails.application.credentials.dig(:aws, :region)
Rails.application.credentials.dig(:minio, :bucket),
aws_access_key_id: Rails.application.credentials.dig(:minio, :access_key_id),
aws_secret_access_key: Rails.application.credentials.dig(:minio, :secret_access_key),
aws_region: Rails.application.credentials.dig(:minio, :region)
)
SitemapGenerator::Sitemap.sitemaps_path = "sitemaps/"

View File

@ -19,19 +19,21 @@ build:
# bucket: your_own_bucket-<%= Rails.env %>
amazon_dev:
service: S3
access_key_id: <%= ENV.fetch("AWS_DEV_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws_dev, :access_key_id)) %>
secret_access_key: <%= ENV.fetch("AWS_DEV_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws_dev, :secret_access_key)) %>
region: <%= ENV.fetch("AWS_DEV_REGION", "wnam") %>
bucket: <%= ENV.fetch("AWS_DEV_BUCKET", Rails.application.credentials.dig(:aws_dev, :bucket)) %>
endpoint: <%= ENV.fetch("AWS_DEV_ENDPOINT", Rails.application.credentials.dig(:aws_dev, :endpoint)) %>
access_key_id: <%= ENV.fetch("AWS_DEV_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio_dev, :access_key_id)) %>
secret_access_key: <%= ENV.fetch("AWS_DEV_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio_dev, :secret_access_key)) %>
region: <%= ENV.fetch("AWS_DEV_REGION", Rails.application.credentials.dig(:minio_dev, :region)) %>
bucket: <%= ENV.fetch("AWS_DEV_BUCKET", Rails.application.credentials.dig(:minio_dev, :bucket)) %>
endpoint: <%= ENV.fetch("AWS_DEV_ENDPOINT", Rails.application.credentials.dig(:minio_dev, :endpoint)) %>
force_path_style: <%= ENV.fetch("AWS_DEV_FORCE_PATH_STYLE", Rails.application.credentials.dig(:minio_dev, :force_path_style)) %>
amazon:
service: S3
access_key_id: <%= ENV.fetch("AWS_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws, :access_key_id)) %>
secret_access_key: <%= ENV.fetch("AWS_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:aws, :secret_access_key)) %>
region: <%= ENV.fetch("AWS_REGION", "wnam") %>
bucket: <%= ENV.fetch("AWS_BUCKET", Rails.application.credentials.dig(:aws, :bucket)) %>
endpoint: <%= ENV.fetch("AWS_ENDPOINT", Rails.application.credentials.dig(:aws, :endpoint)) %>
access_key_id: <%= ENV.fetch("AWS_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio, :access_key_id)) %>
secret_access_key: <%= ENV.fetch("AWS_SECRET_ACCESS_KEY_ID", Rails.application.credentials.dig(:minio, :secret_access_key)) %>
region: <%= ENV.fetch("AWS_REGION", Rails.application.credentials.dig(:minio, :region)) %>
bucket: <%= ENV.fetch("AWS_BUCKET", Rails.application.credentials.dig(:minio, :bucket)) %>
endpoint: <%= ENV.fetch("AWS_ENDPOINT", Rails.application.credentials.dig(:minio, :endpoint)) %>
force_path_style: <%= ENV.fetch("AWS_DEV_FORCE_PATH_STYLE", Rails.application.credentials.dig(:minio, :force_path_style)) %>
# Remember not to checkin your GCS keyfile to a repository
# google:

View File

@ -10,11 +10,6 @@
# AdminUser.create!(email: 'admin@example.com', password: 'password', password_confirmation: 'password') if Rails.env.development?
AdminUser.create!(email: 'admin@example.com', password: 'password', password_confirmation: 'password')
# WeatherArt.delete_all
# City.delete_all
# Country.delete_all
# Region.delete_all
# 创建区域
regions = Region.create!([
{
@ -22,46 +17,14 @@ regions = Region.create!([
code: 'AS'
# },
# {
# name: 'Southeast Asia',
# code: 'SEA'
# },
# {
# name: 'East Asia',
# code: 'EA'
# },
# {
# name: 'Middle East',
# code: 'ME'
# },
# {
# name: 'Africa',
# code: 'AF'
# },
# {
# name: 'North Africa',
# code: 'NA'
# },
# {
# name: 'Sub-Saharan Africa',
# code: 'SSA'
# },
# {
# name: 'Europe',
# code: 'EU'
# },
# {
# name: 'North America',
# code: 'NAM'
# },
# {
# name: 'South America',
# code: 'SAM'
# },
# {
# name: 'Central America',
# code: 'CAM'
# },
# {
# name: 'Oceania',
# code: 'OC'
}
@ -133,9 +96,6 @@ Country.create!([
])
# 创建城市
# Dir[Rails.root.join('db/seeds/cities/*.rb')].sort.each do |file|
# require file
# end
china = Country.find_by code: 'CN'
City.create!([
{

View File

@ -1,22 +0,0 @@
australia = Country.find_by code: 'AU'
City.create!([
{
name: 'Sydney',
latitude: -33.8688,
longitude: 151.2093,
country: australia,
timezone: 'Australia/Sydney',
active: true,
priority: 80
},
{
name: 'Melbourne',
latitude: -37.8136,
longitude: 144.9631,
country: australia,
timezone: 'Australia/Melbourne',
active: true,
priority: 75
}
])

View File

@ -1,13 +0,0 @@
bangladesh = Country.find_by code: 'BD'
City.create!([
{
name: 'Dhaka',
latitude: 23.8103,
longitude: 90.4125,
country: bangladesh,
timezone: 'Asia/Dhaka',
active: true,
priority: 85
}
])

View File

@ -1,13 +0,0 @@
brazil = Country.find_by code: 'BR'
City.create!([
{
name: 'Rio de Janeiro',
latitude: -22.9068,
longitude: -43.1729,
country: brazil,
timezone: 'America/Sao_Paulo',
active: true,
priority: 80
}
])

View File

@ -1,10 +0,0 @@
canada = Country.find_by code: 'CA'
City.create!(
name: 'Toronto',
latitude: 43.6532,
longitude: -79.3832,
priority: 50,
country: canada,
timezone: 'America/Toronto',
active: true
)

View File

@ -1,346 +0,0 @@
china = Country.find_by code: 'CN'
City.create!([
{
name: 'Shanghai',
latitude: 31.2304,
longitude: 121.4737,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Beijing',
latitude: 39.9042,
longitude: 116.4074,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Shenzhen',
latitude: 22.5431,
longitude: 114.0579,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Guangzhou',
latitude: 23.1291,
longitude: 113.2644,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Chengdu',
latitude: 30.5728,
longitude: 104.0668,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Tianjin',
latitude: 39.3434,
longitude: 117.3616,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Wuhan',
latitude: 30.5928,
longitude: 114.3055,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Dongguan',
latitude: 23.0208,
longitude: 113.7518,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Chongqing',
latitude: 29.4316,
longitude: 106.9123,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: "Xi'an",
latitude: 34.3416,
longitude: 108.9398,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Hangzhou',
latitude: 30.2741,
longitude: 120.1551,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Foshan',
latitude: 23.0219,
longitude: 113.1216,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Nanjing',
latitude: 32.0603,
longitude: 118.7969,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Hong Kong',
latitude: 22.3193,
longitude: 114.1694,
country: china,
timezone: 'Asia/Hong_Kong',
active: true,
priority: 100
},
{
name: 'Shenyang',
latitude: 41.8057,
longitude: 123.4315,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Zhengzhou',
latitude: 34.7472,
longitude: 113.6249,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Qingdao',
latitude: 36.0671,
longitude: 120.3826,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Suzhou',
latitude: 31.2990,
longitude: 120.5853,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Changsha',
latitude: 28.2282,
longitude: 112.9388,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Jinan',
latitude: 36.6512,
longitude: 117.1201,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Kunming',
latitude: 25.0389,
longitude: 102.7183,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Harbin',
latitude: 45.8038,
longitude: 126.5340,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Shijiazhuang',
latitude: 38.0428,
longitude: 114.5149,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Hefei',
latitude: 31.8206,
longitude: 117.2272,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Dalian',
latitude: 38.9140,
longitude: 121.6147,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Xiamen',
latitude: 24.4798,
longitude: 118.0819,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Nanning',
latitude: 22.8170,
longitude: 108.3665,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Changchun',
latitude: 43.8171,
longitude: 125.3235,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Taiyuan',
latitude: 37.8706,
longitude: 112.5489,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'New Taipei City',
latitude: 25.0120,
longitude: 121.4657,
country: china,
timezone: 'Asia/Taipei',
active: true,
priority: 100
},
{
name: 'Guiyang',
latitude: 26.6470,
longitude: 106.6302,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Wuxi',
latitude: 31.4914,
longitude: 120.3119,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Shantou',
latitude: 23.3535,
longitude: 116.6822,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Ürümqi',
latitude: 43.8256,
longitude: 87.6168,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Zhongshan',
latitude: 22.5415,
longitude: 113.3926,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Ningbo',
latitude: 29.8683,
longitude: 121.5440,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Fuzhou',
latitude: 26.0745,
longitude: 119.2965,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
},
{
name: 'Nanchang',
latitude: 28.6820,
longitude: 115.8579,
country: china,
timezone: 'Asia/Shanghai',
active: true,
priority: 100
}
])

View File

@ -1,13 +0,0 @@
egypt = Country.find_by code: 'EG'
City.create!([
{
name: 'Alexandria',
latitude: 31.2001,
longitude: 29.9187,
country: egypt,
timezone: 'Africa/Cairo',
active: true,
priority: 100
}
])

View File

@ -1,13 +0,0 @@
france = Country.find_by code: 'FRA'
City.create!([
{
name: 'Paris',
latitude: 48.8566,
longitude: 2.3522,
country: france,
timezone: 'Europe/Paris',
active: true,
priority: 100
}
])

View File

@ -1,22 +0,0 @@
germany = Country.find_by code: 'DE'
City.create!([
{
name: 'Frankfurt',
latitude: 50.1109,
longitude: 8.6821,
country: germany,
timezone: 'Europe/Berlin',
active: true,
priority: 100
},
{
name: 'Berlin',
latitude: 52.5200,
longitude: 13.4050,
country: germany,
timezone: 'Europe/Berlin',
active: true,
priority: 100
}
])

View File

@ -1,22 +0,0 @@
india = Country.find_by code: 'IN'
City.create!([
{
name: 'Mumbai',
latitude: 19.0760,
longitude: 72.8777,
country: india,
timezone: 'Asia/Kolkata',
active: true,
priority: 100
},
{
name: 'Bengaluru',
latitude: 12.9716,
longitude: 77.5946,
country: india,
timezone: 'Asia/Kolkata',
active: true,
priority: 100
}
])

View File

@ -1,22 +0,0 @@
japan = Country.find_by code: 'JP'
City.create!([
{
name: 'Tokyo',
latitude: 35.6762,
longitude: 139.6503,
country: japan,
timezone: 'Asia/Tokyo',
active: true,
priority: 100
},
{
name: 'Yokohama',
latitude: 35.4437,
longitude: 139.6380,
country: japan,
timezone: 'Asia/Tokyo',
active: true,
priority: 100
}
])

View File

@ -1,13 +0,0 @@
mexico = Country.find_by code: 'MX'
City.create!([
{
name: 'Mexico City',
latitude: 19.4326,
longitude: -99.1332,
country: mexico,
timezone: 'America/Mexico_City',
active: true,
priority: 100
}
])

View File

@ -1,13 +0,0 @@
nigeria = Country.find_by code: 'NG'
City.create!([
{
name: 'Lagos',
latitude: 6.5244,
longitude: 3.3792,
country: nigeria,
timezone: 'Africa/Lagos',
active: true,
priority: 100
}
])

View File

@ -1,13 +0,0 @@
pakistan = Country.find_by code: 'PK'
City.create!([
{
name: 'Lahore',
latitude: 31.5204,
longitude: 74.3587,
country: pakistan,
timezone: 'Asia/Karachi',
active: true,
priority: 100
}
])

View File

@ -1,22 +0,0 @@
russia = Country.find_by code: 'RU'
City.create!([
{
name: 'Moscow',
latitude: 55.7558,
longitude: 37.6173,
country: russia,
timezone: 'Europe/Moscow',
active: true,
priority: 100
},
{
name: 'Sankt Petersburg',
latitude: 59.9311,
longitude: 30.3609,
country: russia,
timezone: 'Europe/Moscow',
active: true,
priority: 100
}
])

View File

@ -1,13 +0,0 @@
saudi_arabia = Country.find_by code: 'CA'
City.create!([
{
name: 'Riyadh',
latitude: 24.7136,
longitude: 46.6753,
country: saudi_arabia,
timezone: 'Asia/Riyadh',
active: true,
priority: 100
}
])

View File

@ -1,13 +0,0 @@
singapore = Country.find_by code: 'SG'
City.create!([
{
name: 'Singapore',
latitude: 1.3521,
longitude: 103.8198,
country: singapore,
timezone: 'Asia/Singapore',
active: true,
priority: 100
}
])

View File

@ -1,13 +0,0 @@
south_korea = Country.find_by code: 'KR'
City.create!([
{
name: 'Seoul',
latitude: 37.5665,
longitude: 126.9780,
country: south_korea,
timezone: 'Asia/Seoul',
active: true,
priority: 100
}
])

View File

@ -1,13 +0,0 @@
thailand = Country.find_by code: 'TH'
City.create!([
{
name: 'Bangkok',
latitude: 13.7563,
longitude: 100.5018,
country: thailand,
timezone: 'Asia/Bangkok',
active: true,
priority: 100
}
])

View File

@ -1,22 +0,0 @@
turkey = Country.find_by code: 'TR'
City.create!([
{
name: 'İstanbul',
latitude: 41.0082,
longitude: 28.9784,
country: turkey,
timezone: 'Europe/Istanbul',
active: true,
priority: 100
},
{
name: 'Ankara',
latitude: 39.9334,
longitude: 32.8597,
country: turkey,
timezone: 'Europe/Istanbul',
active: true,
priority: 100
}
])

View File

@ -1,13 +0,0 @@
uk = Country.find_by code: 'GB'
City.create!([
{
name: 'London',
latitude: 51.5074,
longitude: -0.1278,
country: uk,
timezone: 'Europe/London',
active: true,
priority: 100
}
])

View File

@ -1,40 +0,0 @@
usa = Country.find_by code: 'US'
City.create!([
{
name: 'San Francisco',
latitude: 37.7749,
longitude: -122.4194,
country: usa,
timezone: 'America/Los_Angeles',
active: true,
priority: 100
},
{
name: 'Chicago',
latitude: 41.8781,
longitude: -87.6298,
country: usa,
timezone: 'America/Chicago',
active: true,
priority: 100
},
{
name: 'New York City',
latitude: 40.7128,
longitude: -74.0060,
country: usa,
timezone: 'America/New_York',
active: true,
priority: 100
},
{
name: 'Los Angeles',
latitude: 34.0522,
longitude: -118.2437,
country: usa,
timezone: 'America/Los_Angeles',
active: true,
priority: 100
}
])

View File

@ -1,22 +0,0 @@
vietnam = Country.find_by code: 'VN'
City.create!([
{
name: 'Ho Chi Minh City',
latitude: 10.8231,
longitude: 106.6297,
country: vietnam,
timezone: 'Asia/Ho_Chi_Minh',
active: true,
priority: 100
},
{
name: 'Hanoi',
latitude: 21.0285,
longitude: 105.8542,
country: vietnam,
timezone: 'Asia/Ho_Chi_Minh',
active: true,
priority: 100
}
])

View File

@ -32,7 +32,7 @@ namespace :geo do
region.update!(
name: data["name"],
code: data["name"],
translations: data["translations"],
translations: data["translations"].to_json.to_s,
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"],

View File

@ -0,0 +1,7 @@
require "test_helper"
class RssControllerTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
end