In my previous articles I covered the various aspects of Elixir—a modern functional programming language. Today, however, I would like to step aside from the language itself and discuss a very fast and reliable MVC framework called Phoenix that is written in Elixir.
This framework emerged nearly five years ago and has received some traction since then. Of course, it is not as popular as Rails or Django yet, but it does have great potential and I really like it.
In this article we are going to see how to introduce I18n in Phoenix applications. What is I18n, you ask? Well, it is a numeronym that means "internationalization", as there are exactly 18 characters between the first letter "i" and the last "n". Probably, you have also met an L10n numeronym which means "localization". Developers these days are so lazy they can't even write a couple of extra characters, eh?
Internationalization is a very important process, especially if you foresee the application being used by people from all around the world. After all, not everyone knows English well, and having the app translated into a user's native language gives a good impression.
It appears that the process of translating Phoenix applications is somewhat different from, say, translating Rails apps (but quite similar to the same process in Django). To translate Phoenix applications, we use quite a popular solution called Gettext, which has been around for more than 25 years already. Gettext works with special types of files, namely PO and POT, and supports features like scoping, pluralization, and other goodies.
So in this post I am going to explain to you what Gettext is, how PO differs from POT, how to localize messages in Phoenix, and where to store translations. Also we are going to see how to switch the application's locale and how to work with pluralization rules and domains.
Shall we start?
Gettext is a battle-tested open-source internationalization tool initially introduced by Sun Microsystems in 1990. In 1995, GNU released its own version of Gettext, which is now considered to be the most popular out there (the latest version was 0.19.8 at the time of writing this article). Gettext may be used to create multilingual systems of any size and type, from web apps to operational systems. This solution is quite complex, and we are not going to discuss all its features, of course. The full Gettext documentation can be found at gnu.org.
Gettext provides you all the necessary tools to perform localization and presents some requirements on how translation files should be named and organized. Two file types are used to host translations: PO and MO.
PO (Portable Object) files store translations for given strings as well as pluralization rules and metadata. These files have quite a simple structure and can be easily edited by a human, so in this article we will stick to them. Each PO file contains translations (or part of the translations) for a single language and should be stored in a directory named after this language: en, fr, de, etc.
MO (Machine Object) files contain binary data not meant to be edited directly by a human. They are harder to work with, and discussing them is out of the scope of this article.
To make things more complex, there are also POT (Portable Object Template) files. They host only strings of data to translate, but not the translations themselves. Basically, POT files are used only as blueprints to create PO files for various locales.
Okay, so now let's proceed to practice! If you'd like to follow along, make sure you have installed the following:
Create a new sample application without a database by running:
mix phx.new i18ndemo --no-ecto
--no-ecto says that the database should not be utilized by the app (Ecto is a tool to communicate with the DB itself). Note that the generator might require a couple of minutes to prepare everything.
Now use cd to go to the newly created i18ndemo folder and run the following command to boot the server:
mix phx.server
Next, open the browser and navigate to http://localhost:4000, where you should see a "Welcome to Phoenix!" message.
What's interesting about our Phoenix app and, specifically, the welcoming message is that Gettext is already being used by default. Go ahead and open the demo/lib/demo_web/templates/page/index.html.eex file which acts as a default starting page. Remove everything except for this code:
<div class="jumbotron">
<h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2>
</div>This welcoming message utilizes a <a href="https://hexdocs.pm/gettext/Gettext.html#gettext/2" rel="noopener" target="_blank">gettext</a> function which accepts a string to translate as the first argument. This string can be considered as a translation key, though it is somewhat different from the keys used in Rails I18n and some other frameworks. In Rails we would have used a key like page.welcome, whereas here the translated string is a key itself. So, if the translation cannot be found, we can display this string directly. Even a user who knows English poorly can get at least a basic sense of what's going on.
This approach is quite handy actually—stop for a second and think about it. You have an application where all messages are in English. If you'd like to internationalize it, in the simplest case all you have to do is wrap your messages with the gettext function and provide translations for them (later we will see that the process of extracting the keys can be easily automated, which speeds things up even more).
Okay, let's return to our small code snippet and take a look at the second argument passed to gettext: name: "Phoenix". This is a so-called binding—a parameter wrapped with %{} that we'd like to interpolate into the given translation. In this example, there is only one parameter called name.
We can also add one more message to this page for demonstration purposes:
<div class="jumbotron">
<h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2>
<h3><%= gettext "We are using version %6.4.17",
version: "1.3" %></h3>
</div>Now that we have two messages on the root page, where should we add translations for them? It appears that all translations are stored under the priv/gettext folder, which has a predefined structure. Let's take a moment to discuss how Gettext files should be organized (this applies not only to Phoenix but to any app using Gettext).
First of all, we should create a folder named after the locale it is going to store translations for. Inside, there should be a folder called LC_MESSAGES containing one or multiple .po files with the actual translations. In the simplest case, you'd have one default.po file per locale. default here is the domain's (or scope's) name. Domains are used to divide translations into various groups: for example, you might have domains named admin, wysiwig, cart, and other. This is convenient when you have a large application with hundreds of messages. For smaller apps, however, having a sole default domain is enough.
So our file structure might look like this:
To starting creating PO files, we first need the corresponding template (POT). We can create it manually, but I'm too lazy to do it this way. Let's run the following command instead:
mix gettext.extract
It is a very handy tool that scans the project's files and checks whether Gettext is used anywhere. After the script finishes its job, a new priv/gettext/default.pot file containing strings to translate will be created.
As we've already learned, POT files are templates, so they store only the keys themselves, not the translations, so do not modify such files manually. Open a newly created file and take a look at its contents:
## This file is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here as no
## effect: edit them in PO (`.po`) files instead.
msgid ""
msgstr ""
#: lib/demo_web/templates/page/index.html.eex:3
msgid "We are using version %6.4.17"
msgstr ""
#: lib/demo_web/templates/page/index.html.eex:2
msgid "Welcome to %{name}!"
msgstr ""Convenient, isn't it? All our messages were inserted automatically, and we can easily see exactly where they are located. msgid, as you've probably guessed, is the key, whereas msgstr is going to contain a translation.
The next step is, of course, generating a PO file. Run:
mix gettext.merge priv/gettext
This script is going to utilize the default.pot template and create a default.po file in the priv/gettext/en/LC_MESSAGES folder. For now, we have only an English locale, but support for another language will be added in the next section as well.
By the way, it is possible to create or update the POT template and all PO files in one go by using the following command:
mix gettext.extract --merge
Now let's open the priv/gettext/en/LC_MESSAGES/default.po file, which has the following contents:
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
#: lib/demo_web/templates/page/index.html.eex:3
msgid "We are using version %6.4.17"
msgstr ""
#: lib/demo_web/templates/page/index.html.eex:2
msgid "Welcome to %{name}!"
msgstr ""This is the file where we should perform the actual translation. Of course, it makes little sense to do so because the messages are already in English, so let's proceed to the next section and add support for a second language.
Naturally, the default locale for Phoenix applications is English, but this setting can be changed easily by tweaking the config/config.exs file. For example, let's set the default locale to Russian (feel free to stick with any other language of your choice):
config :demo, I18ndemoWeb.Gettext, default_locale: "ru"
It is also a good idea to specify the full list of all supported locales:
config :demo, I18ndemoWeb.Gettext, default_locale: "ru", locales: ~w(en ru)
Now what we need to do is generate a new PO file containing translations for the Russian locale. It can be done by running the gettext.merge script again, but with a --locale switch:
mix gettext.merge priv/gettext --locale ru
Obviously, a priv/gettext/ru/LC_MESSAGES folder with the .po files inside will be generated. Note, by the way, that apart from the default.po file, we also have errors.po. This is a default place to translate error messages, but in this article we are going to ignore it.
Now tweak the priv/gettext/ru/LC_MESSAGES/default.po by adding some translations:
#: lib/demo_web/templates/page/index.html.eex:3
msgid "We are using version %6.4.17"
msgstr "Используется версия %6.4.17"
#: lib/demo_web/templates/page/index.html.eex:2
msgid "Welcome to %{name}!"
msgstr "Добро пожаловать в приложение %{name}!"Now, depending on the chosen locale, Phoenix will render either English or Russian translations. But hold on! How can we actually switch between locales in our application? Let's proceed to the next section and find out!
Now that some translations are present, we need to enable our users to switch between locales. It appears that there is a third-party plug for that called set_locale. It works by extracting the chosen locale from the URL or Accept-Language HTTP header. So, to specify a locale in the URL, you would type http://localhost:4000/en/some_path. If the locale is not specified (or if an unsupported language was requested), one of two things will happen:
Accept-Language HTTP header and this locale is supported, the user will be redirected to a page with the corresponding locale.Open the mix.exs file and drop in set_locale to the deps function:
defp deps do
[
# ...
{:set_locale, "~> 0.2.1"}
]
endWe must also add it to the application function:
def application do
[
mod: {Demo.Application, []},
extra_applications: [:logger, :runtime_tools, :set_locale]
]
endNext, install everything:
mix deps.get
Our router located at lib/demo_web/router.ex requires some changes as well. Specifically, we need to add a new plug to the :browser pipeline:
pipeline :browser do
# ...
plug SetLocale, gettext: DemoWeb.Gettext, default_locale: "ru"
endAlso, create a new scope:
scope "/:locale", DemoWeb do
pipe_through :browser
get "/", PageController, :index
endAnd that's it! You can boot the server and navigate to http://localhost:4000/ru and http://localhost:4000/en. Note that the messages are translated properly, which is exactly what we need!
Alternatively, you may code a similar feature yourself by utilizing a Module plug. A small example can be found in the official Phoenix guide.
One last thing to mention is that in some cases you might need to enforce a specific locale. To do that, simply utilize a with_locale function:
Gettext.with_locale I18ndemoWeb.Gettext, "en", fn ->
MyApp.I18ndemoWeb.gettext("test")
endWe have learned the fundamentals of using Gettext with Phoenix, so the time has come to discuss slightly more complex things. Pluralization is one of them. Basically, working with plural and singular forms is a very common though potentially complex task. Things are more or less obvious in English as you have "1 apple", "2 apples", "9000 apples" etc (though "1 ox", "2 oxen"!).
Unfortunately, in some other languages like Russian or Polish, the rules are more complex. For example, in the case of apples, you'd say "1 яблоко", "2 яблока", "9000 яблок". But luckily for us, Phoenix has a <a href="https://hexdocs.pm/gettext/Gettext.Plural.html" rel="noopener" target="_blank">Gettext.Plural</a> behavior (you may see the behavior in action in one of my previous articles) that supports many different languages. Therefore all we have to do is take advantage of the <a href="https://hexdocs.pm/gettext/Gettext.html#ngettext/4" rel="noopener" target="_blank">ngettext</a> function.
This function accepts three required arguments: a string in singular form, a string in plural form, and count. The fourth argument is optional and can contain bindings that should be interpolated into the translation.
Let's see ngettext in action by saying how much money the user has by modifying the demo/lib/demo_web/templates/page/index.html.eex file:
<p>
<%= ngettext "You have one buck. Ow :(", "You have %{count} bucks", 540 %>
</p>%{count} is an interpolation that will be replaced with a number (540 in this case). Don't forget to update the template and all PO files after adding the above string:
mix gettext.extract --merge
You will see that a new block was added to both default.po files:
msgid "You have one buck. Ow :("
msgid_plural "You have %{count} bucks"
msgstr[0] ""
msgstr[1] ""We have not one but two keys here at once: in singular and in plural forms. msgstr[0] is going to contain some text to display when there is only one message. msgstr[1], of course, contains the text to show when there are multiple messages. This is okay for English, but not enough for Russian where we need to introduce a third case:
msgid "You have one buck. Ow :("
msgid_plural "You have %{count} bucks"
msgstr[0] "У 1 доллар. Маловато будет!"
msgstr[1] "У вас %{count} доллара"
msgstr[2] "У вас %{count} долларов"Case 0 is used for 1 buck, and case 1 for zero or few bucks. Case 2 is used otherwise.
Another topic that I wanted to discuss in this article is devoted to domains. As we already know, domains are used to scope translations, mainly in large applications. Basically, they act like namespaces.
After all, you may end up in a situation when the same key is used in multiple places, but should be translated a bit differently. Or when you have way too many translations in a single default.po file and would like to split them somehow. That's when domains can come in really handy.
Gettext supports multiple domains out of the box. All you have to do is utilize the <a href="https://hexdocs.pm/gettext/Gettext.html#dgettext/3" rel="noopener" target="_blank">dgettext</a> function, which works nearly the same as gettext. The only difference is that it accepts the domain name as the first argument. For instance, let's introduce a notification domain to, well, display notifications. Add three more lines of code to the demo/lib/demo_web/templates/page/index.html.eex file:
<p>
<%= dgettext "notifications", "Heads up: %{msg}", msg: "something has happened!" %>
</p>Now we need to create new POT and PO files:
mix gettext.extract --merge
After the script finishes doing its job, notifications.pot as well as two notifications.po files will be created. Note once again that they are named after the domain. All you have to do now is add translation for the Russian language by modifying the priv/ru/LC_MESSAGES/notifications.po file:
msgid "Heads up: %{msg}}"
msgstr "Внимание: %{msg}"What if you would like to pluralize a message stored under a given domain? This is as simple as utilizing a <a href="https://hexdocs.pm/gettext/Gettext.html#dngettext/5" rel="noopener" target="_blank">dngettext</a> function. It works just like ngettext but also accepts a domain's name as the first argument:
dgettext "domain", "Singular string %{msg}", "Plural string %{msg}", 10, msg: "demo"In this article, we have seen how to introduce internationalization in a Phoenix application with the help of Gettext. You have learned what Gettext is and what type of files it works with. We have this solution in action, have worked with PO and POT files, and utilized various Gettext functions.
Also we've seen a way to add support for multiple locales and added a way to easily switch between them. Lastly, we have seen how to employ pluralization rules and how to scope translations with the help of domains.
Hopefully, this article was useful to you! If you'd like to learn more about Gettext in the Phoenix framework, you may refer to the official guide, which provides useful examples and API reference for all the available functions.
I thank you for staying with me and see you soon!
The Best Small Business Web Designs by DesignRush
/
2Create Modern Vue Apps Using Create-Vue and Vite
/
8Pros and Cons of Using WordPress
/
-5How to Fix the “There Has Been a Critical Error in Your Website” Error in WordPress
How To Fix The “There Has Been A Critical Error in Your Website” Error in WordPress
How to Create a Privacy Policy Page in WordPress
/
8WordPress Website Maintenance Guide For Beginners
/
8How Long Does It Take to Learn JavaScript?
/
-1How Long Does it Take to Learn JavaScript
/
1The Best Way to Deep Copy an Object in JavaScript
/
-2
4
-5Adding and Removing Elements From Arrays in JavaScript
/
-2Create a JavaScript AJAX Post Request: With and Without jQuery
/
85 Real-Life Uses for the JavaScript reduce() Method
/
-9How to Enable or Disable a Button With [removed] jQuery vs. Vanilla
/
2How to Enable or Disable a Button With [removed] jQuery vs Vanilla
/
-3Confirm Yes or No With JavaScript
/
-3How to Change the URL in [removed] Redirecting
/
-315+ Best WordPress Twitter Widgets
/
-227 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/
1121 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/
-1130 HTML Best Practices for Beginners
/
-831 Best WordPress Calendar Plugins and Widgets (With 5 Free Plugins)
/
525 Ridiculously Impressive HTML5 Canvas Experiments
/
-4
1How to Implement Email Verification for New Members
/
4How to Create a Simple Web-Based Chat Application
/
730 Popular WordPress User Interface Elements
/
-4Top 18 Best Practices for Writing Super Readable Code
/
2Best Affiliate WooCommerce Plugins Compared
/
-1018 Best WordPress Star Rating Plugins
/
1510+ Best WordPress Twitter Widgets
/
320+ Best WordPress Booking and Reservation Plugins
/
5Working With Tables in React: Part Two
/
-3Best CSS Animations and Effects on CodeCanyon
/
-1730 CSS Best Practices for Beginners
/
3How to Create a Custom WordPress Plugin From Scratch
/
310 Best Responsive HTML5 Sliders for Images and Text… and 3 Free Options
/
-516 Best Tab and Accordion Widget Plugins for WordPress
/
-518 Best WordPress Membership Plugins and 5 Free Plugins
/
525 Best WooCommerce Plugins for Products, Pricing, Payments and More
/
210 Best WordPress Twitter Widgets
1 /
1312 Best Contact Form PHP Scripts for 2020
/
-420 Popular WordPress User Interface Elements
/
-410 Best WordPress Star Rating Plugins
/
-312 Best CSS Animations on CodeCanyon
/
-112 Best WordPress Booking and Reservation Plugins
/
120 Best WordPress Calendar Plugins and Widgets
/
-212 Elegant CSS Pricing Tables for Your Latest Web Project
/
924 Best WordPress Form Plugins for 2020
/
-214 Best PHP Event Calendar and Booking Scripts
/
-1Getting Started With Django: Newly Updated Course
/
-1Create a Blog for Each Category or Department in Your WooCommerce Store
/
28 Best WordPress Booking and Reservation Plugins
/
-15Best Exit Popups for WordPress Compared
/
19Best Exit Popups for WordPress Compared
/
-411 Best Tab & Accordion WordPress Widgets & Plugins
/
-1112 Best Tab & Accordion WordPress Widgets & Plugins
1 /
3New Course: Practical React Fundamentals
/
2Short Course: Better Angular App Architecture With Modules
/
3Preview Our New Course on Angular Material
/
5Build Your Own CAPTCHA and Contact Form in PHP
/
-1Object-Oriented PHP With Classes and Objects
/
1Best Practices for ARIA Implementation
/
-5Accessible Apps: Barriers to Access and Getting Started With Accessibility
/
-3Dramatically Speed Up Your React Front-End App Using Lazy Loading
/
1815 Best Modern JavaScript Admin Templates for React, Angular, and Vue.js
/
-1515 Best Modern JavaScript Admin Templates for React, Angular and Vue.js
/
319 Best JavaScript Admin Templates for React, Angular, and Vue.js
/
3New Course: Build an App With JavaScript and the MEAN Stack
/
11Hands-on With ARIA: Accessibility Recipes for Web Apps
/
810 Best WordPress Facebook Widgets
13Hands-on With ARIA: Accessibility for eCommerce
/
6New eBooks Available for Subscribers
/
-9Hands-on With ARIA: Homepage Elements and Standard Navigation
/
10
-6Site Accessibility: Getting Started With ARIA
/
5How Secure Are Your JavaScript Open-Source Dependencies?
/
-3New Course: Secure Your WordPress Site With SSL
/
-3Testing Components in React Using Jest and Enzyme
/
-9Testing Components in React Using Jest: The Basics
/
-515 Best PHP Event Calendar and Booking Scripts
/
-2Create Interactive Gradient Animations Using Granim.js
/
9How to Build Complex, Large-Scale Vue.js Apps With Vuex
1Examples of Dependency Injection in PHP With Symfony Components
/
-5Set Up Routing in PHP Applications Using the Symfony Routing Component
1 /
10A Beginner’s Guide to Regular Expressions in JavaScript
/
9Introduction to Popmotion: Custom Animation Scrubber
/
-5Introduction to Popmotion: Pointers and Physics
/
-10New Course: Connect to a Database With Laravel’s Eloquent ORM
/
-5How to Create a Custom Settings Panel in WooCommerce
/
-14Building the DOM faster: speculative parsing, async, defer and preload
1 /
320 Useful PHP Scripts Available on CodeCanyon
3 /
50How to Find and Fix Poor Page Load Times With Raygun
/
1Introduction to the Stimulus Framework
/
-11Single-Page React Applications With the React-Router and React-Transition-Group Modules
/
1
712 Best Contact Form PHP Scripts
1 /
-3Getting Started With the Mojs Animation Library: The ShapeSwirl and Stagger Modules
/
1Getting Started With the Mojs Animation Library: The Shape Module
/
-3Getting Started With the Mojs Animation Library: The HTML Module
/
4Project Management Considerations for Your WordPress Project
/
-78 Things That Make Jest the Best React Testing Framework
/
3Creating an Image Editor Using CamanJS: Layers, Blend Modes, and Events
/
-1New Short Course: Code a Front-End App With GraphQL and React
/
2Creating an Image Editor Using CamanJS: Applying Basic Filters
/
-14Creating an Image Editor Using CamanJS: Creating Custom Filters and Blend Modes
/
-1Modern Web Scraping With BeautifulSoup and Selenium
/
1Challenge: Create a To-Do List in React
1 /
-1Deploy PHP Web Applications Using Laravel Forge
/
-2Getting Started With the Mojs Animation Library: The Burst Module
/
6
-1010 Things Men Can Do to Support Women in Tech
/
-3A Gentle Introduction to Higher-Order Components in React: Best Practices
/
-5Challenge: Build a React Component
/
-4A Gentle Introduction to HOC in React: Learn by Example
/
3A Gentle Introduction to Higher-Order Components in React
/
-6Creating Pretty Popup Messages Using SweetAlert2
/
5Creating Stylish and Responsive Progress Bars Using ProgressBar.js
/
1818 Best Contact Form PHP Scripts for 2022
/
-3How to Make a Real-Time Sports Application Using Node.js
/
-1Creating a Blogging App Using Angular & MongoDB: Delete Post
/
-15Creating a Blogging App Using Angular & MongoDB: Show Post
/
8Creating a Blogging App Using Angular & MongoDB: Home
/
8
4Creating a Blogging App Using Angular & MongoDB: Login
/
13Creating Your First Angular App: Implement Routing
/
13Persisted WordPress Admin Notices: Part 4
/
-3Creating Your First Angular App: Components, Part 2
/
4Persisted WordPress Admin Notices: Part 3
/
4
9Persisted WordPress Admin Notices: Part 2
/
-2Create Your First Angular App: Storing and Accessing Data
/
3Persisted WordPress Admin Notices: Part 1
/
-2Error and Performance Monitoring for Web & Mobile Apps Using Raygun
/
5Using Luxon for Date and Time in JavaScript
7 /
-7How to Create an Audio Oscillator With the Web Audio API
/
-5How to Cache Using Redis in Django Applications
/
220 Essential WordPress Utilities to Manage Your Site
/
-6Introduction to API Calls With React and Axios
/
16Beginner’s Guide to Angular 4: HTTP
/
12Rapid Web Deployment for Laravel With GitHub, Linode, and RunCloud.io
/
10Beginners Guide to Angular 4: Routing
/
-8Beginner’s Guide to Angular 4: Components
/
3Creating a Drop-Down Menu for Mobile Pages
/
6Introduction to Forms in Angular 4: Writing Custom Form Validators
/
4Getting Started With Redux: Connecting Redux With React
/
15Getting Started With Redux: Learn by Example
/
15Getting Started With Redux: Why Redux?
/
10Understanding Recursion With JavaScript
/
-2How to Auto Update WordPress Salts
/
-4How to Download Files in Python
/
-3
-21Eloquent Mutators and Accessors in Laravel
1 /
15
-10
1210 Best HTML5 Sliders for Images and Text
/
7Site Authentication in Node.js: User Signup
/
-3Creating a Task Manager App Using Ionic: Part 2
/
3Creating a Task Manager App Using Ionic: Part 1
/
2Introduction to Forms in Angular 4: Reactive Forms
/
2
4Introduction to Forms in Angular 4: Template-Driven Forms
/
224 Essential WordPress Utilities to Manage Your Site
/
-225 Essential WordPress Utilities to Manage Your Site
/
-11Get Rid of Bugs Quickly Using BugReplay
1 /
-14Manipulating HTML5 Canvas Using Konva: Part 1, Getting Started
/
610 Must-See Easy Digital Downloads Extensions for Your WordPress Site
/
-222 Best WordPress Booking and Reservation Plugins
/
-4Understanding ExpressJS Routing
/
-615 Best WordPress Star Rating Plugins
/
11Creating Your First Angular App: Basics
/
-1Inheritance and Extending Objects With JavaScript
/
-9Introduction to the CSS Grid Layout With Examples
1 /
4
6Performant Animations Using KUTE.js: Part 5, Easing Functions and Attributes
/
-9Performant Animations Using KUTE.js: Part 4, Animating Text
/
-13Performant Animations Using KUTE.js: Part 3, Animating SVG
/
-8New Course: Code a Quiz App With Vue.js
/
-1Performant Animations Using KUTE.js: Part 2, Animating CSS Properties
/
3
8Performant Animations Using KUTE.js: Part 1, Getting Started
/
110 Best Responsive HTML5 Sliders for Images and Text (Plus 3 Free Options)
/
-3Single-Page Applications With ngRoute and ngAnimate in AngularJS
/
-13Deferring Tasks in Laravel Using Queues
/
-5
2Site Authentication in Node.js: User Signup and Login
/
11Working With Tables in React, Part Two
/
-17Working With Tables in React, Part One
/
4How to Set Up a Scalable, E-Commerce-Ready WordPress Site Using ClusterCS
/
-4New Course on WordPress Conditional Tags
/
3TypeScript for Beginners, Part 5: Generics
/
1
-14Building With Vue.js 2 and Firebase
6 /
14
-8Best Unique Bootstrap JavaScript Plugins
/
-8
-2Essential JavaScript Libraries and Frameworks You Should Know About
/
7
1Vue.js Crash Course: Create a Simple Blog Using Vue.js
/
-13Build a React App With a Laravel RESTful Back End: Part 1, Laravel 5.5 API
/
6API Authentication With Node.js
/
-4Beginner’s Guide to Angular: HTTP
/
7Beginner’s Guide to Angular: Routing
/
-4Beginners Guide to Angular: Routing
/
-4Beginner’s Guide to Angular: Services
/
3How to Create a Custom Authentication Guard in Laravel
/
6Learn Computer Science With [removed] Part 3, Loops
/
-5Build Web Applications Using Node.js
/
1Learn Computer Science With [removed] Part 4, Functions
/
3Learn Computer Science With [removed] Part 2, Conditionals
/
11Learn Computer Science With [removed] Part 1, The Basics
/
-2Create Interactive Charts Using Plotly.js, Part 5: Pie and Gauge Charts
/
-9Create Interactive Charts Using Plotly.js, Part 4: Bubble and Dot Charts
/
16Create Interactive Charts Using Plotly.js, Part 3: Bar Charts
/
-12Awesome JavaScript Libraries and Frameworks You Should Know About
/
-4Create Interactive Charts Using Plotly.js, Part 2: Line Charts
/
-8Bulk Import a CSV File Into MongoDB Using Mongoose With Node.js
/
-6Build a To-Do API With Node, Express, and MongoDB
/
-7Getting Started With End-to-End Testing in Angular Using Protractor
/
9TypeScript for Beginners, Part 4: Classes
/
-16Object-Oriented Programming With JavaScript
/
510 Best Affiliate WooCommerce Plugins Compared
/
6Stateful vs. Stateless Functional Components in React
/
8Make Your JavaScript Code Robust With Flow
/
-3Build a To-Do API With Node and Restify
/
8Testing Components in Angular Using Jasmine: Part 2, Services
/
2Testing Components in Angular Using Jasmine: Part 1
/
-1Creating a Blogging App Using React, Part 6: Tags
/
-10React Crash Course for Beginners, Part 3
/
3React Crash Course for Beginners, Part 2
/
8React Crash Course for Beginners, Part 1
/
2Set Up a React Environment, Part 4
1 /
10Set Up a React Environment, Part 3
/
-11New Course: Get Started With Phoenix
/
-9Set Up a React Environment, Part 2
/
1Command Line Basics and Useful Tricks With the Terminal
/
3How to Create a Real-Time Feed Using Phoenix and React
/
-4Build a React App With a Laravel Back End: Part 2, React
/
3Build a React App With a Laravel RESTful Back End: Part 1, Laravel 9 API
/
-5Creating a Blogging App Using React, Part 5: Profile Page
/
-1Pagination in CodeIgniter: The Complete Guide
/
21JavaScript-Based Animations Using Anime.js, Part 4: Callbacks, Easings, and SVG
JavaScript-Based Animations Using Anime.js, Part 3: Values, Timeline, and Playback
/
-1Learn to Code With [removed] Part 1, The Basics
/
410 Elegant CSS Pricing Tables for Your Latest Web Project
/
-8Getting Started With the Flux Architecture in React
/
-3Getting Started With Matter.js: The Composites and Composite Modules
/
2Getting Started With Matter.js: The Engine and World Modules
/
1610 More Popular HTML5 Projects for You to Use and Study
/
-23Understand the Basics of Laravel Middleware
/
-3Iterating Fast With Django & Heroku
/
8Creating a Blogging App Using React, Part 4: Update & Delete Posts
/
4Creating a jQuery Plugin for Long Shadow Design
/
-11How to Register & Use Laravel Service Providers
2 /
-13Unit Testing in React: Shallow vs. Static Testing
/
4Creating a Blogging App Using React, Part 3: Add & Display Post
/
15Creating a Blogging App Using React, Part 2: User Sign-Up
20 /
-3Creating a Blogging App Using React, Part 1: User Sign-In
/
79 More Popular HTML5 Projects for You to Use and Study
/
16Creating a Grocery List Manager Using Angular, Part 2: Managing Items
/
89 Elegant CSS Pricing Tables for Your Latest Web Project
/
-1Dynamic Page Templates in WordPress, Part 3
/
-2Angular vs. React: 7 Key Features Compared
/
6Creating a Grocery List Manager Using Angular, Part 1: Add & Display Items
/
2New eBooks Available for Subscribers in June 2017
/
6Create Interactive Charts Using Plotly.js, Part 1: Getting Started
/
10The 5 Best IDEs for WordPress Development (And Why)
/
-133 Popular WordPress User Interface Elements
/
2New Course: How to Hack Your Own App
/
-11How to Install Yii on Windows or a Mac
/
4What Is a JavaScript Operator?
/
-6How to Register and Use Laravel Service Providers
/
8
waly Good blog post. I absolutely love this…