Packages

phoenix_kit

1.7.78
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules publishing web controller translations.ex
Raw

lib/modules/publishing/web/controller/translations.ex

defmodule PhoenixKit.Modules.Publishing.Web.Controller.Translations do
@moduledoc """
Translation link building for the publishing controller.
Handles building translation/language switcher links for:
- Group listing pages
- Individual post pages
"""
alias PhoenixKit.Modules.Languages
alias PhoenixKit.Modules.Languages.DialectMapper
alias PhoenixKit.Modules.Publishing
alias PhoenixKit.Modules.Publishing.ListingCache
alias PhoenixKit.Modules.Publishing.Web.Controller.Language
alias PhoenixKit.Modules.Publishing.Web.Controller.PostRendering
alias PhoenixKit.Modules.Publishing.Web.HTML, as: PublishingHTML
# ============================================================================
# Listing Page Translations
# ============================================================================
@doc """
Build translation links for group listing page.
Accepts posts to avoid redundant list_posts calls.
"""
def build_listing_translations(group_slug, current_language, posts) do
# Get enabled languages - these are the ONLY languages that should show
enabled_languages =
try do
Languages.enabled_locale_codes()
rescue
_ -> ["en"]
end
# Extract base code from current language for comparison
current_base = DialectMapper.extract_base(current_language)
# Get the primary/default language
primary_language = List.first(enabled_languages) || "en"
# For each enabled language, check if there's published content for it
# Only show languages that are explicitly enabled (not just base code matches)
translations =
enabled_languages
|> Enum.filter(fn lang ->
# Check if this specific language has published content (using passed posts)
has_published_content_for_language?(posts, lang)
end)
|> Enum.map(fn lang ->
# Use display_code helper to determine if we show base or full code
display_code = Publishing.get_display_code(lang, enabled_languages)
%{
code: display_code,
display_code: display_code,
name: Language.get_language_name(lang),
flag: Language.get_language_flag(lang),
url: PublishingHTML.group_listing_path(display_code, group_slug),
current: DialectMapper.extract_base(lang) == current_base
}
end)
# Order: primary first, then the rest alphabetically
if Enum.any?(
translations,
&(&1.code == Publishing.get_display_code(primary_language, enabled_languages))
) do
primary_display = Publishing.get_display_code(primary_language, enabled_languages)
{primary, others} = Enum.split_with(translations, &(&1.code == primary_display))
primary ++ Enum.sort_by(others, & &1.code)
else
Enum.sort_by(translations, & &1.code)
end
|> Enum.uniq_by(& &1.code)
end
# Check if a specific enabled language has published content in the group
# ONLY checks for EXACT matches - no base code fallback
# This ensures only languages with actual content show in the public switcher
# Uses passed posts to avoid redundant list_posts calls
defp has_published_content_for_language?(posts, language) do
Enum.any?(posts, fn post ->
# Check if there's published content for this EXACT language only
# Use preloaded language_statuses map
language in (post.available_languages || []) and
Map.get(post.language_statuses, language) == "published"
end)
end
# ============================================================================
# Post Page Translations
# ============================================================================
@doc """
Build translation links for a post page.
"""
def build_translation_links(group_slug, post, current_language, opts \\ []) do
version = Keyword.get(opts, :version)
# Get enabled languages
enabled_languages =
try do
Languages.enabled_locale_codes()
rescue
_ -> ["en"]
end
# Extract base code from current language for comparison
current_base = DialectMapper.extract_base(current_language)
# Use the post's primary language so it appears first in the switcher
primary_language = post[:primary_language] || List.first(enabled_languages) || "en"
# Fetch language_slugs from cache for per-language URL slugs
# Falls back to using post.slug for all languages if cache miss
language_slugs = fetch_language_slugs_from_cache(group_slug, post)
# Include ALL available languages that are published
# This allows legacy/disabled languages to still show in the public switcher
# (they'll be styled differently by the component based on enabled/known flags)
available_and_published =
post.available_languages
|> normalize_languages(current_language)
|> Enum.filter(fn lang ->
translation_published_exact?(group_slug, post, lang)
end)
# Remove legacy base codes when dialect content exists
# e.g., if both "en" and "en-CA" exist, remove "en" to avoid duplicates
deduplicated =
deduplicate_base_and_dialect_codes(available_and_published, enabled_languages)
# Order: primary first (if present), then enabled languages, then disabled ones
languages = order_languages_for_public(deduplicated, enabled_languages, primary_language)
Enum.map(languages, fn lang ->
# Use display_code helper to determine if we show base or full code
display_code = Publishing.get_display_code(lang, enabled_languages)
is_enabled = language_enabled_for_public?(lang, enabled_languages)
is_known = Languages.get_predefined_language(lang) != nil
# Get the URL slug for this specific language
# This enables SEO-friendly localized URLs (e.g., /es/docs/primeros-pasos)
url_slug_for_lang = Map.get(language_slugs, lang, post.slug)
post_with_url_slug = Map.put(post, :url_slug, url_slug_for_lang)
# Build URL with version if viewing a specific version
url =
if version do
PostRendering.build_version_url(group_slug, post_with_url_slug, display_code, version)
else
PublishingHTML.build_post_url(group_slug, post_with_url_slug, display_code)
end
%{
code: display_code,
display_code: display_code,
name: Language.get_language_name(lang),
flag: Language.get_language_flag(lang),
url: url,
current: DialectMapper.extract_base(lang) == current_base,
enabled: is_enabled,
known: is_known
}
end)
end
# ============================================================================
# Helper Functions
# ============================================================================
# Order languages for public display: primary first, then enabled, then disabled
defp order_languages_for_public(languages, enabled_languages, primary_language) do
{enabled, disabled} =
Enum.split_with(languages, fn lang ->
language_enabled_for_public?(lang, enabled_languages)
end)
# Put primary first if present
{primary, other_enabled} = Enum.split_with(enabled, &(&1 == primary_language))
primary ++ Enum.sort(other_enabled) ++ Enum.sort(disabled)
end
# Remove legacy base codes when dialect content of the same language exists
# This prevents showing both "en" and "en-CA" in the switcher
defp deduplicate_base_and_dialect_codes(languages, _enabled_languages) do
# Separate base codes and dialect codes
{base_codes, dialect_codes} = Enum.split_with(languages, &Language.base_code?/1)
# For each base code, check if any dialect content exists for it
# If so, exclude the base code
filtered_base_codes =
Enum.reject(base_codes, fn base ->
Enum.any?(dialect_codes, fn dialect ->
DialectMapper.extract_base(dialect) == base
end)
end)
# Return dialect codes plus any base codes that don't have dialect alternatives
dialect_codes ++ filtered_base_codes
end
# Fetches language_slugs map from cache for per-language URL slugs
# Returns a map of language -> url_slug for each available language
defp fetch_language_slugs_from_cache(group_slug, post) do
case ListingCache.find_post_by_mode(group_slug, post) do
{:ok, cached_post} ->
cached_post.language_slugs || %{}
{:error, _} ->
# Cache miss - return empty map (will fall back to post.slug)
%{}
end
end
defp normalize_languages([], current_language), do: [current_language]
defp normalize_languages(languages, _current_language) when is_list(languages), do: languages
# Strict check for public display - only shows languages that are:
# 1. Directly in the enabled languages list, OR
# 2. Base codes where any dialect of that base is enabled
# This prevents showing en-US, en-GB etc when only en-CA is enabled
defp language_enabled_for_public?(language, enabled_languages) do
cond do
# Direct match - language code exactly matches an enabled language
language in enabled_languages ->
true
# Base code (e.g., "en") - show if any dialect is enabled
Language.base_code?(language) ->
Enum.any?(enabled_languages, fn enabled_lang ->
DialectMapper.extract_base(enabled_lang) == language
end)
# Dialect (e.g., "en-US") not directly enabled - DON'T show
# This is the key difference from language_enabled?
true ->
false
end
end
# Translation is visible if it exists — status comes from the post level
defp translation_published_exact?(_group_slug, post, language) do
language in (post.available_languages || [])
end
end