Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
@inlang/paraglide-js
Advanced tools
With Paraglide's treeshakeable messages, each page only loads the messages it actually uses. Incremental loading like this would usually take forever to get right, with Paraglide you get it for free.
Paraglide is framework agnostic, but there are framework-specific libraries available. If there is one for your framework you will want to follow its documentation instead. If there isn't, read on.
A few recent comments.
To use Paraglide standalone without a framework, run the following command:
npx @inlang/paraglide-js@latest init
This will:
messages/
folder where your translation files livebuild
script in package.json
Running the Paraglide compiler will generate a src/paraglide
folder. This folder contains all the code that you will use in your app.
Messages are stored in messages/{lang}.json
. To add a message simply add a key-value pair. You can add parameters with curly braces.
// messages/en.json
{
"$schema": "https://inlang.com/schema/inlang-message-format",
+ "greeting": "Hello {name}!"
}
Make sure to re-run the paraglide compiler after editing your messages.
npx @inlang/paraglide-js compile --project ./project.inlang --outdir ./src/paraglide
If you are using Bundler, you can use one of the Bundler Plugins to recompile automatically.
After running the compiler, you can import messages with import * as m from "./paraglide/messages"
.
import * as m from "./paraglide/messages.js"
import { setLanguageTag } from "./paraglide/runtime.js"
m.hello() // Hello world!
m.loginHeader({ name: "Samuel" }) // Hello Samuel, please login to continue.
## Working with the Inlang Message Format
Paraglide is part of the highly modular Inlang Ecosystem which supports many different Message Formats. By default, the Inlang Message Format is used.
It expects messages to be in messages/{lang}.json
relative to your repo root.
//messages/en.json
{
//the $schema key is automatically ignored
"$schema": "https://inlang.com/schema/inlang-message-format",
"hello_world: "Hello World!",
"greeting": "Hello {name}!"
}
The messages/{lang}.json
file contains a flat map of message IDs and their translations. You can use curly braces to insert {parameters}
into translations
Nesting purposely isn't supported and likely won't be. Nested messages are way harder to interact with from complementary tools like the Sherlock IDE Extension, the Parrot Figma Plugin, or the Fink Localization editor. Intellisense also becomes less helpful since it only shows the messages at the current level, not all messages. Additionally enforcing an organization-style side-steps organization discussions with other contributors.
### Complex Formatting
The Message Format is still quite young, so advanced formats like plurals, param-formatting, and markup interpolation are currently not supported but are all on our roadmap.
If you need complex formatting, like plurals, dates, currency, or markup interpolation you can achieve them like so:
For a message with multiple cases, aka a select message, you can define a message for each case & then use a Map in JS to index into it.
import * as m from "./paraglide/messages.js"
const season = {
spring: m.spring,
summer: m.summer,
autumn: m.autumn,
winter: m.winter,
} as const
const msg = season["spring"]() // Hello spring!
For date & currency formatting use the .toLocaleString
method on the Date
or Number
.
import * as m from "./paraglide/messages.js"
import { languageTag } from "./paraglide/runtime.js"
const todaysDate = new Date();
m.today_is_the({
date: todaysDate.toLocaleString(languageTag())
})
const price = 100;
m.the_price_is({
price: price.toLocaleString(languageTag(), {
style: "currency",
currency: "EUR",
})
})
You can put HTML into the messages. This is useful for links and images.
// messages/en.json
{
"you_must_agree_to_the_tos": "You must agree to the <a href='/en/tos'>Terms of Service</a>."
}
// messages/de.json
{
you_must_agree_to_the_tos": "Sie müssen den <a href='/de/agb'>Nutzungsbedingungen</a> zustimmen."
}
There is currently no way to interpolate full-blown components into the messages. If you require components mid-message you will need to create a one-off component for that bit of text.
You can set the language tag by calling setLanguageTag()
with the desired language, or a getter function. Any subsequent calls to either languageTag()
or a message function will use the new language tag.
import { setLanguageTag } from "./paraglide/runtime.js"
import * as m from "./paraglide/messages.js"
setLanguageTag("de")
m.hello() // Hallo Welt!
setLanguageTag(()=>document.documentElement.lang /* en */ )
m.hello() // Hello world!
The language tag is global, so you need to be careful with it on the server to make sure multiple requests don't interfere with each other. Always use a getter-function that returns the current language tag for the current request.
You will need to call setLanguageTag
on both the server and the client since they run in separate processes.
Messages aren't reactive, so you will need to trigger a re-render when the language changes. You can register a callback using onSetLanguageTag()
. It is called whenever the language tag changes.
If you are using a framework-specific library this is done for you.
import { setLanguageTag, onSetLanguageTag } from "./paraglide/runtime.js"
import * as m from "./paraglide/messages.js"
onSetLanguageTag((newLanguageTag) => {
console.log(`The language changed to ${newLanguageTag}`)
})
setLanguageTag("de") // The language changed to de
setLanguageTag("en") // The language changed to en
Things to know about onSetLanguageTag()
:
onSetLanguageTag
shouldn't be used on the server.You can import a message in a specific language from paraglide/messages/{lang}.js
.
import * as m from "./paraglide/messages/de.js"
m.hello() // Hallo Welt
If you want to force a language, but don't know which language ahead of time you can pass the languageTag
option as the second parameter to a message function. This is often handy on the server.
import * as m from "./paraglide/messages.js"
const msg = m.hello({ name: "Samuel" }, { languageTag: "de" }) // Hallo Samuel!
Paraglide consciously discourages lazy-loading translations since it causes a render-fetch waterfall which seriously hurts your Web Vitals. Learn more about why lazy-loading is bad & what to do instead in our blog post on lazy-loading.
If you want to do it anyway, lazily import the language-specific message files.
const lazyGerman = await import("./paraglide/messages/de.js")
lazyGerman.hello() // Hallo Welt
If you are using a bundler you should use the corresponding plugin. The plugin will keep your Message Functions up-to-date by compiling whenever your messages change and before building your app.
Most of the configuration is done in ./project.inlang/settings.json
, except for paraglide's output directory, which needs to be passed in when calling the compiler.
You can declare which languages you support in the languageTags
array.
// project.inlang/settings.json
{
"languageTags": ["en", "de"]
}
Create the corresponding messages/{lang}.json
files and get translating!
If you want your language files to be in a different location you can change the pathPattern
of the Inlang-Message-Format plugin.
// project.inlang/settings.json
{
"plugin.inlang.messageFormat": {
- "pathPattern": "./messages/{languageTag}.json"
+ "pathPattern": "./i18n/{languageTag}.json"
}
}
Find examples of how to use Paraglide on CodeSandbox or in our GitHub repository.
Paraglide uses a compiler to generate JS functions from your messages. We call these "message functions".
Message Functions are fully typed using JSDoc. They are exported individually from the messages.js
file making them tree-shakable. When called, they return a translated string. Message functions aren't reactive in any way, if you want a translation in another language you will need to re-call them.
This design avoids many edge cases with reactivity, lazy-loading, and namespacing that other i18n libraries have to work around.
In addition to the message functions, ParaglideJS also emits a runtime. The runtime is used to set the language tag. It contains less than 50 LOC (lines of code) and is less than 300 bytes minified & gzipped.
Paraglide consists of four main parts:
Part | Description |
---|---|
Compiler | Compiles messages into tree-shakable message functions |
Messages | The compiled tree-shakable message functions |
Runtime | A runtime that resolves the language tag of the current user |
Framework Library | (optional) A framework library that adjusts the runtime for different frameworks |
The compiler loads an Inlang project and compiles the messages into tree-shakable and typesafe message functions.
Input
// messages/en.json
{
"hello": "Hello {name}!"
}
Output
// src/paraglide/messages/en.js
/**
* @param {object} params
* @param {string} params.name
*/
export const hello = (params) => `Hello ${params.name}!`
Paraglide-Framework-Library integrates with a framework's lifecycle. It does two things:
setLanguageTag()
at appropriate times to set the languageonSetLanguageTag()
, usually by navigating or relading the page.Additionally, it may provide convenience features such as localised routing.
Many popular frameworks already have libraries available, check out the list of available framework libraries.
If there isn't one for your framework, you can write your own. This example adapts Paraglide to a fictitious full-stack framework.
import {
setLanguageTag,
onSetLanguageTag,
type AvailableLanguageTag,
} from "../paraglide/runtime.js"
import { isServer, isClient, request, render } from "@example/framework"
import { detectLanguage } from "./utils.js"
if (isServer) {
// On the server the language tag needs to be resolved on a per-request basis.
// Pass a getter function that resolves the language from the correct request
const detectLanguage = (request: Request): AvailableLanguageTag => {
//your logic ...
}
setLanguageTag(() => detectLanguage(request))
}
if (isClient) {
// On the client, the language tag can be resolved from
// the document's html lang tag.
setLanguageTag(() => document.documentElement.lang)
// When the language changes we want to re-render the page in the new language
// Here we just navigate to the new route
// Make sure to call `onSetLanguageTag` after `setLanguageTag` to avoid an infinite loop.
onSetLanguageTag((newLanguageTag) => {
window.location.pathname = `/${newLanguageTag}${window.location.pathname}`
})
}
// Render the app once the setup is done
render((page) => (
<html lang={request.languageTag}>
<body>{page}</body>
</html>
))
Of course, we're not done yet! We plan on adding the following features to Paraglide JS soon:
Paraglide JS is part of the Inlang ecosystem and integrates nicely with all the other Inlang-compatible tools.
As a developer, you will love the Sherlock VS Code extension.
If you are working with translators or designers you will find these tools useful:
FAQs
[![Inlang-ecosystem compatibility badge](https://cdn.jsdelivr.net/gh/opral/monorepo@main/inlang/assets/md-badges/inlang.svg)](https://inlang.com)
The npm package @inlang/paraglide-js receives a total of 18,613 weekly downloads. As such, @inlang/paraglide-js popularity was classified as popular.
We found that @inlang/paraglide-js demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.