Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
August 14, 2022 05:12 am GMT

Build a Multi-Language Countdown Component

Some years ago, I built a countdown component with support for multiple languages. Editors had to fill in labels like "days", "hours" and "minutes" for each language in both singular and plural versions. Luckily, things have greatly improved since then!

In this tutorial, we'll be building a vanilla countdown component that'll work with all languages without having to fill out any labels per language.

We'll be using the Intl.RelativeTimeFormat and Intl.NumberFormat API's to build a countdown component like this:

Countdown

Then, by simply changing the lang-attribute, we'll create variations like these:

lang="fa-IR"

Arabic

lang="zh-Hans-CN-u-nu-hanidec"

Chinese

lang="fr-FR"

French

Cool, right? Let's get started!

HTML

We'll be using the <time>-tag, with the end-date set in the datetime-property:

<time datetime="2023-01-01"></time>

If you want your countdown to end at a specific time also, add that to the string:

<time datetime="2022-08-12T09:30:00+02:00"></time> 

NOTE: Remember to add the timezone offset of the location, where the countdown expires. This shouldn't be the server-time, as your location might be in central Europe, while your server/CDN is in the US. In the example above, the offset is +02:00.

JavaScript

We'll create a new function with a single argument, element, which is a reference to the main element (<time>):

function countDown(element) { ... }

Next, we'll set up some consts and defaults:

Locale

The locale is the most important part. This code will look for a lang-attribute on the main element, then if not found on the page itself, and finally return a fallback, using en-US:

const locale = element.lang || document.documentElement.getAttribute('lang') || 'en-US';

End-date/time

The end-time is the datetime-attribute from the main element, converted to time:

const endTime = new Date(element.getAttribute('datetime')).getTime();

API's

Finally, we create an instance of Intl.RelativeTimeFormat as well as storing a const with the locale-value of zero (more on that later!):

const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });const zero = new Intl.NumberFormat(locale).format(0);

Showing Time

Now, for the code that returns and formats the time and it's sub-parts:

const showTime = () => {  const remainingTime = getRemainingTime(endTime);  element.innerHTML =     timePart(Math.floor(remainingTime / (24 * 60 * 60 * 1000)), 'day') +    timePart(Math.floor((remainingTime / (60 * 60 * 1000)) % 24), 'hour') +    timePart(Math.floor((remainingTime / (60 * 1000)) % 60), 'minute') +    timePart(Math.floor((remainingTime / 1000) % 60), 'second');}

The function calls a helper-method, that deduct the current time from 'end-time':

const getRemainingTime = (endTime, currentTime = new Date().getTime()) => endTime - currentTime;

... as well as timePart, the most important function, that returns locale-formatted time:

const timePart = (part, type) => {  const parts = rtf.formatToParts(part === 0 ? 2 : part, type);  if (parts && parts.length === 3) parts.shift();  const [unit, label] = parts;   return `<span><strong>${part === 0 ? zero : unit.value}</strong><small>${label.value}</small></span>`}

formatToParts returns an array of time-units and labels in the locale-language. We spread these into unit and label, and output them in strong> and <small>-tags (feel free to replace with whatever tags you want!).

requestAnimationFrame

The showTime-function need to call itself continuously, for which we use requestAnimationFrame:

if (remainingTime >= 1000) requestAnimationFrame(showTime);

Zero is Plural

Now, you might wonder why I call formatToParts with 2, if the value itself is 0 (zero). That's because Intl.RelativeTimeFormat will return the string "now" (in the locale-language), if the value is zero and no label (for some reason).

We don't want that, but neither do we want to show the english zero in languages that have their own zero!

That's why, in the beginning, we declared this:

const zero = new Intl.NumberFormat(locale).format(0);

For the label, we need a value larger than 1. If we use "seconds" as an example, the value 1, will return the label "second", while the value 2 will return "seconds". Zero is plural (you say "zero seconds", not "zero second"), hence the 2

Confused? So was I!

Finally, to initialize and run it:

requestAnimationFrame(showTime);

Phew! A lot of code, but only approx. 400 bytes when minified and gzipped!

CSS

The CSS is simply a grid, see the demo below for details. I like to use CSS Custom Props for the parts of a component, that can have variations. The format I prefer is [component]-[part]-[emmet abbrevation of property], so:

.variant {--countdown-bgc: hsl(0, 35%, 45%);--countdown-time-bgc: hsl(0, 35%, 80%);--countdown-time-lbl-c: hsl(0, 35%, 15%);--countdown-time-val-c: hsl(0, 35%, 25%);}

Demo

Below is a Codepen. Feel free to fork it and change the locales:


Original Link: https://dev.to/madsstoumann/build-a-multi-language-countdown-component-260j

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To