mix ash_phoenix_translations.install (ash_phoenix_translations v1.0.0)

View Source

Installs AshPhoenixTranslations into your Phoenix application.

This task automates the initial setup process for AshPhoenixTranslations, configuring your application, creating necessary files, and generating example resources to help you get started quickly.

Features

  • Backend Selection: Choose between Database or Gettext backend
  • Configuration Generation: Automatic config.exs setup
  • Migration Creation: Database backend migration generation
  • Gettext Setup: Complete Gettext directory structure
  • Example Resource: Working example to learn from
  • Interactive Installation: Step-by-step guidance

Basic Usage

# Install with database backend (default)
mix ash_phoenix_translations.install

# Install with gettext backend
mix ash_phoenix_translations.install --backend gettext

# Install without modifying config
mix ash_phoenix_translations.install --no-config

# Skip migration generation
mix ash_phoenix_translations.install --no-migration

Options

  • --backend - The default backend to use (database, gettext). Default: database
  • --no-config - Skip config file modifications
  • --no-gettext - Skip Gettext setup even if selected as backend
  • --no-migration - Skip migration generation for database backend

Installation Steps

1. Configuration

Adds the following to config/config.exs:

config :ash_phoenix_translations,
  default_backend: :database,
  default_locales: [:en, :es, :fr],
  cache_ttl: 3600,
  cache_backend: :ets

2. Backend-Specific Setup

Database Backend

Creates a migration file in priv/repo/migrations/:

defmodule Repo.Migrations.CreateTranslationsTable do
  use Ecto.Migration

  def change do
    create table(:translations) do
      add :resource_type, :string, null: false
      add :resource_id, :uuid, null: false
      add :field, :string, null: false
      add :locale, :string, null: false
      add :value, :text
      add :metadata, :map, default: %{}

      timestamps()
    end

    create index(:translations, [:resource_type, :resource_id])
    create index(:translations, [:locale])
    create unique_index(:translations,
      [:resource_type, :resource_id, :field, :locale])
  end
end

After installation, run:

mix ecto.migrate

Gettext Backend

Creates the following directory structure in priv/gettext/:

priv/gettext/
 en/
    LC_MESSAGES/
        translations.po
 es/
    LC_MESSAGES/
        translations.po
 fr/
     LC_MESSAGES/
         translations.po

After installation, extract and compile translations:

mix ash_phoenix_translations.extract
mix gettext.merge priv/gettext
mix compile.gettext

3. Example Resource

Creates lib/example/product.ex with a working example:

defmodule Example.Product do
  use Ash.Resource,
    extensions: [AshPhoenixTranslations]

  translations do
    translatable_attribute :name,
      locales: [:en, :es, :fr],
      required: [:en]

    translatable_attribute :description,
      locales: [:en, :es, :fr],
      translate: true

    backend :database
    cache_ttl 7200
  end

  attributes do
    uuid_primary_key :id
    attribute :sku, :string
    attribute :price, :decimal
    timestamps()
  end

  actions do
    defaults [:create, :read, :update, :destroy]
  end
end

Post-Installation Setup

1. Add Extension to Your Resources

defmodule MyApp.Shop.Product do
  use Ash.Resource,
    extensions: [AshPhoenixTranslations]

  translations do
    translatable_attribute :name, locales: [:en, :es, :fr]
    translatable_attribute :description, locales: [:en, :es, :fr]
    backend :database
  end
end

2. Configure Router Plugs

Add to your Phoenix router:

pipeline :browser do
  # ... other plugs
  plug AshPhoenixTranslations.Plugs.SetLocale
  plug AshPhoenixTranslations.Plugs.LoadTranslations
end

3. Import Helpers in Views

In your MyAppWeb module:

def html do
  quote do
    # ... other imports
    import AshPhoenixTranslations.Helpers
  end
end

4. Use in Templates

# Access translated attributes
<%= t(@product, :name) %>
<%= t(@product, :description) %>

# Locale selector
<%= locale_select(@conn, [:en, :es, :fr]) %>

# Get all translations for a field
<%= all_translations(@product, :name) %>

Workflow Examples

New Project Setup

# 1. Install with database backend
mix ash_phoenix_translations.install --backend database

# 2. Run migration
mix ecto.migrate

# 3. Add to your first resource
# Edit lib/my_app/shop/product.ex and add translations block

# 4. Start your application
mix phx.server

Adding to Existing Project

# 1. Install without modifying config (you'll configure manually)
mix ash_phoenix_translations.install --no-config

# 2. Review generated files
# - Check migration file
# - Review example resource

# 3. Manually add configuration to config/config.exs
# 4. Add plugs to router.ex
# 5. Import helpers in your view module

Gettext-Based Project

# 1. Install with Gettext backend
mix ash_phoenix_translations.install --backend gettext

# 2. Extract strings from resources
mix ash_phoenix_translations.extract

# 3. Merge with existing Gettext files
mix gettext.merge priv/gettext

# 4. Translate .po files
# Edit priv/gettext/*/LC_MESSAGES/translations.po

# 5. Compile translations
mix compile.gettext

Configuration Options

Default Configuration

The installer creates this configuration:

config :ash_phoenix_translations,
  default_backend: :database,        # or :gettext
  default_locales: [:en, :es, :fr],  # Your supported locales
  cache_ttl: 3600,                   # 1 hour cache
  cache_backend: :ets                # ETS-based caching

Advanced Configuration

You can extend the configuration after installation:

config :ash_phoenix_translations,
  default_backend: :database,
  default_locales: [:en, :es, :fr, :de, :it],
  cache_ttl: 7200,
  cache_backend: :ets,

  # Fallback chain
  fallback_chain: [:es, :en],

  # Security
  supported_locales: [:en, :es, :fr, :de, :it],

  # Performance
  lazy_load: true,
  preload_translations: false

Backend Comparison

Database Backend

Pros:

  • Dynamic translation updates without deployment
  • Audit trail with timestamps
  • Per-resource translation storage
  • Ideal for user-editable content

Cons:

  • Additional database queries
  • Requires migration
  • More storage usage

Best For:

  • E-commerce product catalogs
  • CMS content
  • User-generated content
  • Applications requiring frequent translation updates

Gettext Backend

Pros:

  • Compiled translations (fast runtime)
  • Standard .po file format
  • Integrates with existing Gettext tools
  • CAT tool compatible

Cons:

  • Requires recompilation for updates
  • Less dynamic
  • Not per-resource

Best For:

  • Static content
  • UI labels and messages
  • Applications with stable translations
  • Integration with professional translation workflows

Troubleshooting

Configuration Not Applied

Problem: Configuration doesn't seem to take effect

Solution:

  1. Restart your application after installation
  2. Check config/config.exs for syntax errors
  3. Ensure configuration is not overridden in environment-specific configs

Migration Fails

Problem: Database migration fails to run

Solution:

  1. Check PostgreSQL version (JSONB requires 9.4+)
  2. Verify database connection in config/dev.exs
  3. Check for table name conflicts
  4. Ensure migrations directory exists

Gettext Files Not Created

Problem: priv/gettext/ directory not created

Solution:

  1. Ensure --no-gettext flag was not used
  2. Check directory permissions
  3. Manually create directory structure if needed

Example Resource Conflicts

Problem: Example resource causes compilation errors

Solution:

  1. Delete lib/example/product.ex if not needed
  2. Rename the module to avoid conflicts
  3. Use it as a reference, then remove

Security Considerations

Atom Exhaustion Prevention

The installer configures locale validation to prevent atom exhaustion:

# In config/config.exs (generated)
config :ash_phoenix_translations,
  supported_locales: [:en, :es, :fr]  # Whitelist only

Only these locales can be converted to atoms, preventing malicious input from exhausting the atom table.

Database Security

The generated migration includes:

  • NOT NULL constraints: Prevent incomplete records
  • Unique indexes: Prevent duplicate translations
  • JSONB type: Secure JSON storage (PostgreSQL)

After installation, you'll commonly use:

Examples

Full Database Backend Setup

# Install
mix ash_phoenix_translations.install --backend database

# Run migration
mix ecto.migrate

# Add to resource
# lib/my_app/shop/product.ex
defmodule MyApp.Shop.Product do
  use Ash.Resource,
    extensions: [AshPhoenixTranslations]

  translations do
    translatable_attribute :name, locales: [:en, :es, :fr]
    backend :database
  end
end

# Test in IEx
iex -S mix
iex> alias MyApp.Shop.Product
iex> product = Product.create!(%{name_translations: %{en: "Coffee", es: "Café"}})
iex> AshPhoenixTranslations.translate(product, :name, :es)
"Café"

Full Gettext Backend Setup

# Install
mix ash_phoenix_translations.install --backend gettext

# Extract strings
mix ash_phoenix_translations.extract

# Merge and edit
mix gettext.merge priv/gettext
# Edit priv/gettext/es/LC_MESSAGES/translations.po

# Compile
mix compile.gettext

# Test
iex -S mix
iex> AshPhoenixTranslations.translate(product, :name, :es)
"Café"

Minimal Installation

# Skip everything except example
mix ash_phoenix_translations.install --no-config --no-migration --no-gettext

# Review the example resource
cat lib/example/product.ex

# Manually configure as needed

Summary

Functions

Callback implementation for Mix.Task.run/1.

Functions

run(args)

Callback implementation for Mix.Task.run/1.