Building a Laravel Translation Package – Wiring Up The Frontend
Published on by Joe Dixon
In the last installment of this series, we talked through the process of manipulating the translations in our application’s language files into a format where we are now in a position to start interacting with them. In this article, we’ll be wiring up the frontend ready to start building out the user interface which will aid users with the process of translation management.
The UI will be developed using the community favorites, Tailwind CSS and Vue.js.
The Approach
Like it or loathe it, I’m taking a leaf straight out of the Caleb Porzio book of embracing the backend for this package. In his talk, Caleb presents the notion of taking the emphasis away from using Javascript to build single page applications including making it responsible for retrieving all of its data as well as things like routing and form submissions. Instead, he suggests using strengths of Laravel to produce a more hybrid approach. Taking inspiration from this, we’ll be leveraging our controllers and blade views to pass the data required by our Vue.js components to add the necessary dynamic functionality. Laravel Mix will be utilized to build and compile the package assets.
Routes
To start, we’ll need to tell Laravel where to load our routes from. One way to do this is by using the loadRoutesFrom
method inside of our package’s TranslationServiceProvider
. This method is inherited from the parent Illuminate\Support\ServiceProvider
class.
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
This allows us to define all the package routes in one file in the same way you would in your Laravel application.
Route::group(config('translation.route_group_config') + ['namespace' => 'JoeDixon\\Translation\\Http\\Controllers'], function ($router) { $router->get(config('translation.ui_url'), 'LanguageController@index') ->name('languages.index'); $router->get(config('translation.ui_url').'/{language}/translations', 'LanguageTranslationController@index') ->name('languages.translations.index'); ...});
In this file, we use the Route::group()
method to pull in any custom group configuration which is merged with the namespace from where the package controllers reside.
The only other thing to note is that the configuration is again used to dynamically register the routes where the user wants them. If left to the default configuration options, the ui_url
is set to languages
. As such, the above routes will be registered as follows:
my-app.test/languagesmy-app.test/languages/{language}/translations
In this file, routes are defined to list, create and store languages and list, create, store and update translations.
Controllers
There is no wiring in the service provider required for controllers. As long as they are found at the namespace defined in the routes file, everything will work as expected.
In each of the controllers, access to the translation drivers we created in the previous article in this series, is required from just about every method. As such, we’ll resolve it from the container in the constructor.
public function __construct(Translation $translation){ $this->translation = $translation;}
Now, regardless of the driver, it’s trivial to use the functionality we have already built to interact with the project’s translations from within the controller.
public function index(Request $request){ $languages = $this->translation->allLanguages(); return view('translation::languages.index', compact('languages'));}
The only thing which may look a little alien here is the way the view is loaded. translation::
simply tells Laravel to load the view from the translation package namespace which Laravel is made aware of in the service provider. More on that to follow.
Views
First, we need to tell Laravel where the routes for the package should be loaded from.
$this->loadViewsFrom(__DIR__.'/../resources/views', 'translation');
By using the loadViewsFrom
method in the boot
method of the package service provider, Laravel knows that our routes should be loaded from /package/root/resources/views
using the translation
namespace. Using a namespace mitigates the risk of views conflicting between packages or even the main application.
$this->publishes([ __DIR__.'/../resources/views' => resource_path('views/vendor/translation'),]);
The above line tells Laravel where the views can be found and where they should be copied to should the package consumer wish to pull them into their application.
In this case, they would be copied from /package/root/resources/views
to views/vendor/translation
within the resource path of the application.
Typically, this is done so the consumer is able to edit them to suit their own requirements.
Assets
For my own sanity, I like to use Laravel Mix to build assets for my packages. It offers a great developer experience and is quick and easy to get up and running.
I start by running npm init
from the root of the project and following the instructions to setup the environment.
Now it’s possible to install Laravel Mix by running npm install laravel-mix --save-dev
. This will install all the required dependencies as well as add Laravel Mix as a development dependency to the package.json
file.
Next, we need to copy the webpack.mix.js
file which ships with Mix to the root of the project so we can start configuring the build.
cp node_modules/laravel-mix/setup/webpack.mix.js ./
CSS
Next, we will install and configure the CSS framework, Tailwind, into our package. To do this, pull in the dependency by running npm install tailwindcss --save-dev
.
With the dependencies available, we can now start to configure the installation. First, we’ll generate the Tailwind configuration file. This can be achieved by running ./node_modules/.bin/tailwind init tailwind.js
where tailwind.js
is the name the configuration file will be generated as. This file allows you to tweak the base color settings, fonts and font sizes, widths and heights and much, much more. It’s worth taking a look at this or reading the Tailwind documentation for further information. However, for the purposes of this tutorial, we will continue with the excellent default values.
Finally, we need to tell Laravel Mix to run PostCSS using the Tailwind configuration as part of the build.
To do this, we first need to require the Tailwind PostCSS plugin in the webpack.mix.js
file.
var tailwindcss = require('tailwindcss');
Then as part of the build pipeline, we can use Mix’s postCss
method, passing in the exported function stored in the tailwindcss
variable above to configure this step of the build.
mix.postCss('resources/assets/css/main.css', 'css', [ tailwindcss('./tailwind.js'),]);
This method is saying, take the file at resources/assets/css/main.css
, apply the tailwind PostCSS plugin and output to the css
directory.
Now when we run npm run dev|production|watch
, this process will run automagically.
Javascript
We will develop the UI using Vue.js, so this will need to be pulled in as a dependency. We will also require a little bit of ajax functionality so let’s also install the popular axios package to do the heavy lifting for us.
npm install vue axios
Publishing
Above, we have asked Mix to build the assets into the css
and js
directories, but we need to tell it where to find them in relation to the webpack.mix.js
file. The can be done using the following method.
mix.setPublicPath('public/assets');
In a standard Laravel application, this would be used to publish the built assets to a publicly available location. However, in our package scope, we need to publish them to a location where they can easily be published using the php artisan publish
command. This allows an end user to publish the files to their application where they are free to modify them and keep them within their own version control.
Finally, we need to tell Laravel where to find the files for publishing. This is, again, carried out in the Service Provider.
$this->publishes([ __DIR__.'/../public/assets' => public_path('vendor/translation'),], 'assets');
The first argument is an array where the key represents the path to the assets within the package, and the value represents the location where they should be published within the app. The second argument is a tag defining the type of files being published. This provides the user the flexibility to choose the types of file they wish to publish as an option to the publish command.
Tip
In development, you will likely not want to have to run the publish artisan command every time you want to test your changes. To mitigate this, update the mix.setPublicPath
method to the path you wish to load the assets from in testing. However, don’t forget to change it back when you are ready to commit! An excellent way to manage this state is by using environment variables.
We now have the groundwork in place to start fleshing out the routes, controllers, views, and assets needed to bring the user interface to life, which we’ll cover in the next installment.