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 gettext
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 %2.11.9", 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 %2.11.9" 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 %2.11.9" 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 %2.11.9" msgstr "Используется версия %2.11.9" #: 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"} ] end
We must also add it to the application
function:
def application do [ mod: {Demo.Application, []}, extra_applications: [:logger, :runtime_tools, :set_locale] ] end
Next, 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" end
Also, create a new scope:
scope "/:locale", DemoWeb do pipe_through :browser get "/", PageController, :index end
And 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") end
We 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 Gettext.Plural
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 ngettext
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 dgettext
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 dngettext
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
/Create Modern Vue Apps Using Create-Vue and Vite
/How 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 Long Does It Take to Learn JavaScript?
/The Best Way to Deep Copy an Object in JavaScript
/Adding and Removing Elements From Arrays in JavaScript
/Create a JavaScript AJAX Post Request: With and Without jQuery
/5 Real-Life Uses for the JavaScript reduce() Method
/How to Enable or Disable a Button With JavaScript: jQuery vs. Vanilla
/How to Enable or Disable a Button With JavaScript: jQuery vs Vanilla
/Confirm Yes or No With JavaScript
/How to Change the URL in JavaScript: Redirecting
/15+ Best WordPress Twitter Widgets
/27 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/21 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/30 HTML Best Practices for Beginners
/31 Best WordPress Calendar Plugins and Widgets (With 5 Free Plugins)
/25 Ridiculously Impressive HTML5 Canvas Experiments
/How to Implement Email Verification for New Members
/How to Create a Simple Web-Based Chat Application
/30 Popular WordPress User Interface Elements
/Top 18 Best Practices for Writing Super Readable Code
/Best Affiliate WooCommerce Plugins Compared
/18 Best WordPress Star Rating Plugins
/10+ Best WordPress Twitter Widgets
/20+ Best WordPress Booking and Reservation Plugins
/Working With Tables in React: Part Two
/Best CSS Animations and Effects on CodeCanyon
/30 CSS Best Practices for Beginners
/How to Create a Custom WordPress Plugin From Scratch
/10 Best Responsive HTML5 Sliders for Images and Text… and 3 Free Options
/16 Best Tab and Accordion Widget Plugins for WordPress
/18 Best WordPress Membership Plugins and 5 Free Plugins
/25 Best WooCommerce Plugins for Products, Pricing, Payments and More
/10 Best WordPress Twitter Widgets
1 /12 Best Contact Form PHP Scripts for 2020
/20 Popular WordPress User Interface Elements
/10 Best WordPress Star Rating Plugins
/12 Best CSS Animations on CodeCanyon
/12 Best WordPress Booking and Reservation Plugins
/12 Elegant CSS Pricing Tables for Your Latest Web Project
/24 Best WordPress Form Plugins for 2020
/14 Best PHP Event Calendar and Booking Scripts
/Create a Blog for Each Category or Department in Your WooCommerce Store
/8 Best WordPress Booking and Reservation Plugins
/Best Exit Popups for WordPress Compared
/Best Exit Popups for WordPress Compared
/11 Best Tab & Accordion WordPress Widgets & Plugins
/12 Best Tab & Accordion WordPress Widgets & Plugins
1New Course: Practical React Fundamentals
/Preview Our New Course on Angular Material
/Build Your Own CAPTCHA and Contact Form in PHP
/Object-Oriented PHP With Classes and Objects
/Best Practices for ARIA Implementation
/Accessible Apps: Barriers to Access and Getting Started With Accessibility
/Dramatically Speed Up Your React Front-End App Using Lazy Loading
/15 Best Modern JavaScript Admin Templates for React, Angular, and Vue.js
/15 Best Modern JavaScript Admin Templates for React, Angular and Vue.js
/19 Best JavaScript Admin Templates for React, Angular, and Vue.js
/New Course: Build an App With JavaScript and the MEAN Stack
/Hands-on With ARIA: Accessibility Recipes for Web Apps
/10 Best WordPress Facebook Widgets
13 /Hands-on With ARIA: Accessibility for eCommerce
/New eBooks Available for Subscribers
/Hands-on With ARIA: Homepage Elements and Standard Navigation
/Site Accessibility: Getting Started With ARIA
/How Secure Are Your JavaScript Open-Source Dependencies?
/New Course: Secure Your WordPress Site With SSL
/Testing Components in React Using Jest and Enzyme
/Testing Components in React Using Jest: The Basics
/15 Best PHP Event Calendar and Booking Scripts
/Create Interactive Gradient Animations Using Granim.js
/How to Build Complex, Large-Scale Vue.js Apps With Vuex
1 /Examples of Dependency Injection in PHP With Symfony Components
/Set Up Routing in PHP Applications Using the Symfony Routing Component
1 /A Beginner’s Guide to Regular Expressions in JavaScript
/Introduction to Popmotion: Custom Animation Scrubber
/Introduction to Popmotion: Pointers and Physics
/New Course: Connect to a Database With Laravel’s Eloquent ORM
/How to Create a Custom Settings Panel in WooCommerce
/Building the DOM faster: speculative parsing, async, defer and preload
1 /20 Useful PHP Scripts Available on CodeCanyon
3 /How to Find and Fix Poor Page Load Times With Raygun
/Introduction to the Stimulus Framework
/Single-Page React Applications With the React-Router and React-Transition-Group Modules
12 Best Contact Form PHP Scripts
1 /Getting Started With the Mojs Animation Library: The ShapeSwirl and Stagger Modules
/Getting Started With the Mojs Animation Library: The Shape Module
Getting Started With the Mojs Animation Library: The HTML Module
/Project Management Considerations for Your WordPress Project
/8 Things That Make Jest the Best React Testing Framework
/Creating an Image Editor Using CamanJS: Layers, Blend Modes, and Events
/New Short Course: Code a Front-End App With GraphQL and React
/Creating an Image Editor Using CamanJS: Applying Basic Filters
/Creating an Image Editor Using CamanJS: Creating Custom Filters and Blend Modes
/Modern Web Scraping With BeautifulSoup and Selenium
/Challenge: Create a To-Do List in React
1Deploy PHP Web Applications Using Laravel Forge
/Getting Started With the Mojs Animation Library: The Burst Module
/10 Things Men Can Do to Support Women in Tech
/A Gentle Introduction to Higher-Order Components in React: Best Practices
/Challenge: Build a React Component
/A Gentle Introduction to HOC in React: Learn by Example
/A Gentle Introduction to Higher-Order Components in React
/Creating Pretty Popup Messages Using SweetAlert2
/Creating Stylish and Responsive Progress Bars Using ProgressBar.js
/18 Best Contact Form PHP Scripts for 2022
/How to Make a Real-Time Sports Application Using Node.js
/Creating a Blogging App Using Angular & MongoDB: Delete Post
/Set Up an OAuth2 Server Using Passport in Laravel
/Creating a Blogging App Using Angular & MongoDB: Edit Post
/Creating a Blogging App Using Angular & MongoDB: Add Post
/Introduction to Mocking in Python
/Creating a Blogging App Using Angular & MongoDB: Show Post
/Creating a Blogging App Using Angular & MongoDB: Home
/Creating a Blogging App Using Angular & MongoDB: Login
/Creating Your First Angular App: Implement Routing
/Persisted WordPress Admin Notices: Part 4
/Creating Your First Angular App: Components, Part 2
/Persisted WordPress Admin Notices: Part 3
/Creating Your First Angular App: Components, Part 1
/How Laravel Broadcasting Works
/Persisted WordPress Admin Notices: Part 2
/Create Your First Angular App: Storing and Accessing Data
/Persisted WordPress Admin Notices: Part 1
/Error and Performance Monitoring for Web & Mobile Apps Using Raygun
Using Luxon for Date and Time in JavaScript
7 /How to Create an Audio Oscillator With the Web Audio API
/How to Cache Using Redis in Django Applications
/20 Essential WordPress Utilities to Manage Your Site
/Introduction to API Calls With React and Axios
/Beginner’s Guide to Angular 4: HTTP
/Rapid Web Deployment for Laravel With GitHub, Linode, and RunCloud.io
/Beginners Guide to Angular 4: Routing
/Beginner’s Guide to Angular 4: Services
/Beginner’s Guide to Angular 4: Components
/Creating a Drop-Down Menu for Mobile Pages
/Introduction to Forms in Angular 4: Writing Custom Form Validators
/10 Best WordPress Booking & Reservation Plugins
/Getting Started With Redux: Connecting Redux With React
/Getting Started With Redux: Learn by Example
/Getting Started With Redux: Why Redux?
/How to Auto Update WordPress Salts
/How to Download Files in Python
/Eloquent Mutators and Accessors in Laravel
1 /10 Best HTML5 Sliders for Images and Text
/Site Authentication in Node.js: User Signup
/Creating a Task Manager App Using Ionic: Part 2
/Creating a Task Manager App Using Ionic: Part 1
/Introduction to Forms in Angular 4: Reactive Forms
/Introduction to Forms in Angular 4: Template-Driven Forms
/24 Essential WordPress Utilities to Manage Your Site
/25 Essential WordPress Utilities to Manage Your Site
/Get Rid of Bugs Quickly Using BugReplay
1 /Manipulating HTML5 Canvas Using Konva: Part 1, Getting Started
/10 Must-See Easy Digital Downloads Extensions for Your WordPress Site
22 Best WordPress Booking and Reservation Plugins
/Understanding ExpressJS Routing
/15 Best WordPress Star Rating Plugins
/Creating Your First Angular App: Basics
/Inheritance and Extending Objects With JavaScript
/Introduction to the CSS Grid Layout With Examples
1Performant Animations Using KUTE.js: Part 5, Easing Functions and Attributes
Performant Animations Using KUTE.js: Part 4, Animating Text
/Performant Animations Using KUTE.js: Part 3, Animating SVG
/New Course: Code a Quiz App With Vue.js
/Performant Animations Using KUTE.js: Part 2, Animating CSS Properties
Performant Animations Using KUTE.js: Part 1, Getting Started
/10 Best Responsive HTML5 Sliders for Images and Text (Plus 3 Free Options)
/Single-Page Applications With ngRoute and ngAnimate in AngularJS
/Deferring Tasks in Laravel Using Queues
/Site Authentication in Node.js: User Signup and Login
/Working With Tables in React, Part Two
/Working With Tables in React, Part One
/How to Set Up a Scalable, E-Commerce-Ready WordPress Site Using ClusterCS
/New Course on WordPress Conditional Tags
/TypeScript for Beginners, Part 5: Generics
/Building With Vue.js 2 and Firebase
6 /Best Unique Bootstrap JavaScript Plugins
/Essential JavaScript Libraries and Frameworks You Should Know About
/Vue.js Crash Course: Create a Simple Blog Using Vue.js
/Build a React App With a Laravel RESTful Back End: Part 1, Laravel 5.5 API
/API Authentication With Node.js
/Beginner’s Guide to Angular: HTTP
/Beginner’s Guide to Angular: Routing
/Beginners Guide to Angular: Routing
/Beginner’s Guide to Angular: Services
/Beginner’s Guide to Angular: Components
/How to Create a Custom Authentication Guard in Laravel
/Learn Computer Science With JavaScript: Part 3, Loops
/Build Web Applications Using Node.js
/Learn Computer Science With JavaScript: Part 4, Functions
/Learn Computer Science With JavaScript: Part 2, Conditionals
/Create Interactive Charts Using Plotly.js, Part 5: Pie and Gauge Charts
/Create Interactive Charts Using Plotly.js, Part 4: Bubble and Dot Charts
Create Interactive Charts Using Plotly.js, Part 3: Bar Charts
/Awesome JavaScript Libraries and Frameworks You Should Know About
/Create Interactive Charts Using Plotly.js, Part 2: Line Charts
/Bulk Import a CSV File Into MongoDB Using Mongoose With Node.js
/Build a To-Do API With Node, Express, and MongoDB
/Getting Started With End-to-End Testing in Angular Using Protractor
/TypeScript for Beginners, Part 4: Classes
/Object-Oriented Programming With JavaScript
/10 Best Affiliate WooCommerce Plugins Compared
/Stateful vs. Stateless Functional Components in React
/Make Your JavaScript Code Robust With Flow
/Build a To-Do API With Node and Restify
/Testing Components in Angular Using Jasmine: Part 2, Services
/Testing Components in Angular Using Jasmine: Part 1
/Creating a Blogging App Using React, Part 6: Tags
/React Crash Course for Beginners, Part 3
/React Crash Course for Beginners, Part 2
/React Crash Course for Beginners, Part 1
/Set Up a React Environment, Part 4
1 /Set Up a React Environment, Part 3
/New Course: Get Started With Phoenix
/Set Up a React Environment, Part 2
/Set Up a React Environment, Part 1
/Command Line Basics and Useful Tricks With the Terminal
/How to Create a Real-Time Feed Using Phoenix and React
/Build a React App With a Laravel Back End: Part 2, React
/Build a React App With a Laravel RESTful Back End: Part 1, Laravel 9 API
/Creating a Blogging App Using React, Part 5: Profile Page
/Pagination in CodeIgniter: The Complete Guide
/JavaScript-Based Animations Using Anime.js, Part 4: Callbacks, Easings, and SVG
/JavaScript-Based Animations Using Anime.js, Part 3: Values, Timeline, and Playback
/Learn to Code With JavaScript: Part 1, The Basics
/10 Elegant CSS Pricing Tables for Your Latest Web Project
/Getting Started With the Flux Architecture in React
/Getting Started With Matter.js: The Composites and Composite Modules
Getting Started With Matter.js: The Engine and World Modules
/10 More Popular HTML5 Projects for You to Use and Study
/Understand the Basics of Laravel Middleware
/Iterating Fast With Django & Heroku
/Creating a Blogging App Using React, Part 4: Update & Delete Posts
/Creating a jQuery Plugin for Long Shadow Design
/How to Register & Use Laravel Service Providers
2 /Unit Testing in React: Shallow vs. Static Testing
/Creating a Blogging App Using React, Part 3: Add & Display Post
/Creating a Blogging App Using React, Part 2: User Sign-Up
20 /Creating a Blogging App Using React, Part 1: User Sign-In
/Creating a Grocery List Manager Using Angular, Part 2: Managing Items
/9 Elegant CSS Pricing Tables for Your Latest Web Project
/Dynamic Page Templates in WordPress, Part 3
/Angular vs. React: 7 Key Features Compared
/Creating a Grocery List Manager Using Angular, Part 1: Add & Display Items
New eBooks Available for Subscribers in June 2017
/Create Interactive Charts Using Plotly.js, Part 1: Getting Started
/The 5 Best IDEs for WordPress Development (And Why)
/33 Popular WordPress User Interface Elements
/New Course: How to Hack Your Own App
/How to Install Yii on Windows or a Mac
/What Is a JavaScript Operator?
/How to Register and Use Laravel Service Providers
/
waly Good blog post. I absolutely love this…