diff --git a/app/helpers/better_together/application_helper.rb b/app/helpers/better_together/application_helper.rb index 7470d9d88..9ce38014a 100644 --- a/app/helpers/better_together/application_helper.rb +++ b/app/helpers/better_together/application_helper.rb @@ -97,6 +97,17 @@ def host_community_logo_url rails_storage_proxy_url(attachment) end + # Sets a translated meta description for the current view. Provide the + # translation scope without the `meta.descriptions` prefix. + # + # set_meta_description('communities.show', community_name: @resource.name) + # + # @param scope [String] translation scope under meta.descriptions + # @param options [Hash] interpolation values for the translation + def set_meta_description(scope, **options) + content_for(:meta_description, t("meta.descriptions.#{scope}", **options)) + end + # Builds SEO-friendly meta tags for the current view. Defaults are derived # from translations and fall back to the Open Graph description when set. # rubocop:todo Metrics/MethodLength diff --git a/app/views/better_together/communities/index.html.erb b/app/views/better_together/communities/index.html.erb index f223ec0cc..7c4c301fc 100644 --- a/app/views/better_together/communities/index.html.erb +++ b/app/views/better_together/communities/index.html.erb @@ -2,6 +2,8 @@ <%= resource_class.model_name.human.pluralize %> <% end %> +<% set_meta_description('communities.index', platform_name: host_platform.name) %> +

<%= resource_class.model_name.human.pluralize %>

diff --git a/app/views/better_together/communities/show.html.erb b/app/views/better_together/communities/show.html.erb index c8b0a6d38..84f16d8ad 100644 --- a/app/views/better_together/communities/show.html.erb +++ b/app/views/better_together/communities/show.html.erb @@ -2,6 +2,8 @@ <%= @resource.name %> | <%= resource_class.model_name.human.pluralize %> <% end %> +<% set_meta_description('communities.show', community_name: @resource.name, platform_name: host_platform.name) %> +
diff --git a/app/views/better_together/conversations/index.html.erb b/app/views/better_together/conversations/index.html.erb index 9d9835d37..25ce0f8e3 100644 --- a/app/views/better_together/conversations/index.html.erb +++ b/app/views/better_together/conversations/index.html.erb @@ -2,6 +2,8 @@ <%= BetterTogether::Conversation.model_name.human.pluralize %> <% end %> +<% set_meta_description('conversations.index', platform_name: host_platform.name) %> +
diff --git a/app/views/better_together/conversations/show.html.erb b/app/views/better_together/conversations/show.html.erb index 54e4b36cf..aa35f8c62 100644 --- a/app/views/better_together/conversations/show.html.erb +++ b/app/views/better_together/conversations/show.html.erb @@ -4,6 +4,8 @@ <%= "#{@conversation} | " if @conversation.to_s.present? %><%= @conversation.class.model_name.human.pluralize %> <% end %> +<% set_meta_description('conversations.show', conversation_subject: @conversation.to_s, platform_name: host_platform.name) %> + <%= render 'communicator' do %> <%= render partial: 'better_together/conversations/conversation_content', locals: { conversation: @conversation, messages: @messages, message: @message } %> <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 0d0df6c58..d3dc6d1d9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2101,6 +2101,23 @@ en: last_word_connector: ", and " two_words_connector: " and " words_connector: ", " + og: + default_title: "%{platform_name}" + default_description: "Welcome to %{platform_name}" + page: + title: "%{title} | %{platform_name}" + description_fallback: "Read %{title} on %{platform_name}" + meta: + default_description: "Welcome to %{platform_name}" + page: + description_fallback: "Read %{title} on %{platform_name}" + descriptions: + communities: + index: "Discover communities on %{platform_name}." + show: "Learn about %{community_name} on %{platform_name}." + conversations: + index: "Browse conversations on %{platform_name}." + show: "View conversation %{conversation_subject} on %{platform_name}." time: am: am formats: diff --git a/config/locales/es.yml b/config/locales/es.yml index 58afa9906..afb4af068 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -2104,6 +2104,30 @@ es: last_word_connector: " y " two_words_connector: " y " words_connector: ", " + og: + default_title: "%{platform_name}" + default_description: "Bienvenido a %{platform_name}" + page: + title: "%{title} | %{platform_name}" + description_fallback: "Lee %{title} en %{platform_name}" + meta: + default_description: "Bienvenido a %{platform_name}" + page: + description_fallback: "Lee %{title} en %{platform_name}" + descriptions: + communities: + index: "Descubre comunidades en %{platform_name}." + show: "Conoce %{community_name} en %{platform_name}." + conversations: + index: "Explora tus conversaciones en %{platform_name}." + show: "Ver conversación %{conversation_subject} en %{platform_name}." + errors: + not_found: + title: "404 - Página no encontrada" + description: "La página que buscas pudo haber sido eliminada, cambiada o está temporalmente no disponible." + internal_server_error: + title: "500 - Error interno del servidor" + description: "Lo sentimos, algo salió mal en nuestro servidor." time: am: am formats: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 0b6fd8b8f..9048a85c5 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -2132,6 +2132,30 @@ fr: last_word_connector: " et " two_words_connector: " et " words_connector: ", " + og: + default_title: "%{platform_name}" + default_description: "Bienvenue sur %{platform_name}" + page: + title: "%{title} | %{platform_name}" + description_fallback: "Lire %{title} sur %{platform_name}" + meta: + default_description: "Bienvenue sur %{platform_name}" + page: + description_fallback: "Lire %{title} sur %{platform_name}" + descriptions: + communities: + index: "Découvrir les communautés sur %{platform_name}." + show: "En savoir plus sur %{community_name} sur %{platform_name}." + conversations: + index: "Parcourez vos conversations sur %{platform_name}." + show: "Voir la conversation %{conversation_subject} sur %{platform_name}." + errors: + not_found: + title: "404 - Page non trouvée" + description: "La page que vous recherchez a peut-être été supprimée, changée ou est temporairement indisponible." + internal_server_error: + title: "500 - Erreur interne du serveur" + description: "Nous sommes désolés, une erreur s'est produite de notre côté." time: am: am formats: diff --git a/docs/seo.md b/docs/seo.md new file mode 100644 index 000000000..43ce06bd7 --- /dev/null +++ b/docs/seo.md @@ -0,0 +1,18 @@ +# SEO + +## Meta Descriptions + +- Each high-traffic page should set a concise, unique `content_for :meta_description`. +- Use the `set_meta_description` helper with translation keys under `meta.descriptions`. +- Keep descriptions under 160 characters and include relevant keywords. +- Example: + +```erb +<% set_meta_description('communities.show', community_name: @community.name, platform_name: host_platform.name) %> +``` + +## Best Practices + +- Translate descriptions using `config/locales/*` to support internationalization. +- Prefer dynamic values (e.g., community or conversation names) to ensure uniqueness. +- Review descriptions regularly to avoid duplication and improve click-through rates. diff --git a/spec/helpers/better_together/application_helper_spec.rb b/spec/helpers/better_together/application_helper_spec.rb index 735112991..111ffec4b 100644 --- a/spec/helpers/better_together/application_helper_spec.rb +++ b/spec/helpers/better_together/application_helper_spec.rb @@ -1,9 +1,27 @@ # frozen_string_literal: true require 'rails_helper' +require 'nokogiri' module BetterTogether - RSpec.describe ApplicationHelper do + RSpec.describe ApplicationHelper, type: :helper do + describe '#set_meta_description' do + it 'stores translated description in content_for and renders meta tag' do + allow(helper).to receive(:host_platform).and_return(double(name: 'MyPlatform')) + allow(helper).to receive(:request).and_return(double(original_url: 'http://example.com')) + allow(helper).to receive(:host_community_logo_url).and_return(nil) + + helper.set_meta_description('communities.index', platform_name: 'MyPlatform') + + expected = I18n.t('meta.descriptions.communities.index', platform_name: 'MyPlatform') + expect(view.content_for(:meta_description)).to eq(expected) + + html = Nokogiri::HTML.fragment(helper.seo_meta_tags) + meta = html.at('meta[name="description"]') + expect(meta['content']).to eq(expected) + end + end + describe '#robots_meta_tag' do it 'renders default robots meta tag' do # rubocop:todo RSpec/MultipleExpectations tag = helper.robots_meta_tag