Open a command console, enter your project directory and execute:
$ composer require symfony-javascript/javascript-bundle
Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle:
$ composer require symfony-javascript/javascript-bundle
This command requires you to have Composer installed globally, as explained in the installation chapter of the Composer documentation.
Then, enable the bundle by adding it to the list of registered bundles
in the config/bundles.php
file of your project:
// config/bundles.php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
// ...
SymfonyJavascript\JavascriptBundle\JavascriptBundle::class => ['all' => true],
];
You can configure the bundle if the default values do not suit your project. I recommand changing the extract paths to your needs.
# config/packages/javascript.yaml
javascript:
translation:
# The extract path of the message file.
extract_path: public/build/messages.js
# The locales to be exported
locales: []
# The domains to be exported
domains: []
routing:
# The extract path of the message file.
extract_path: public/build/routes.js
# Defines a list of routes to be exposed or hidden, depending on the whitelist argument. Supports RegEx.
routes: []
# Defines wehther or not the routes parameter will be used as a whitelist.
whitelist: false
The bundle provides two commands to extract your route and translation files. It also provides a command to extract them all.
$ php bin/console javascript:extract:translations
$ php bin/console javascript:extract:routes
$ php bin/console javascript:extract:all
You will need to include the created file in your Javascript. You will need to run these commands once before launching to production, and each time you edit your routes or translations.
Be sure to clear cache before and after your extractions if you have any trouble.
You can chose to use whatever you want, but there are three usable libraries. There is a router, a translator, and a Vue adapter.
Either install everything:
$ yarn add @symfony-javascript/vue-adapter @symfony-javascript/router @symfony-javascript/translator
Or only what you want
$ yarn add @symfony-javascript/vue-adapter
$ yarn add @symfony-javascript/router
$ yarn add @symfony-javascript/translator
The easiest way is to configure the bundle to export the messages
and routes
files as json
in a directory containing everything related to this bundle.
# config/packages/javascript.yaml
javascript:
translation:
extract_path: assets/js/symfony/messages
routing:
extract_path: assets/js/symfony/routes
You will then need to configure both the router
and translator
. Here is a good starter that you can use with minimal changes required.
// assets/js/symfony/index.ts
import Vue, { VueConstructor } from 'vue';
import { VueRouter, VueTranslator } from '@symfony-javascript/vue-adapter';
import { RouterSettings } from '@symfony-javascript/router/dist/router/RouterSettings';
import { Router } from '@symfony-javascript/router/dist/router/Router';
import { TranslatorSettings } from '@symfony-javascript/translator/dist/translator/TranslatorSettings';
import { Translator } from '@symfony-javascript/translator/dist/translator/Translator';
import messages from './messages.json';
import routes from './routes.json';
const debug = process.env.NODE_ENV !== 'production';
const RouterConfig: RouterSettings = {
data: routes,
forceCurrentScheme: true,
fallbackScheme: 'https',
debug,
};
const TranslatorConfig: TranslatorSettings = {
data: messages,
locale: 'fr',
strategy: 'strict',
debug,
};
export const translator = new Translator(TranslatorConfig);
export const router = new Router(RouterConfig);
export default function(Vue: VueConstructor<Vue>) {
Vue.use(VueRouter, RouterConfig);
Vue.use(VueTranslator, TranslatorConfig);
}
Note 1 - I exported a standalone
translator
androuter
in case you need them to configure another extension. It's especially usefull for Vue extension that require internationalization.
Note 2 - This example is in TypeScript, but nothing it's pretty much the same in Javascript, just remove the typings.
// assets/js/main.ts
import Vue from 'vue';
import RegisterSymfony from './symfony';
RegisterSymfony(Vue);
// initialize your Vue below
// assets/js/Components/SomeComponent.vue
<script>
export default {
mounted() {
this.$_('some_message', {}, 'messages', 'en'); // translate something
this.$path('some_route', {}); // generate a route
this.$router.path('some_route', {}); // same as above
this.$router.url('some_route', {}); // same as above, but full URL
this.$router.logoutPath(); // get the logout path
this.$router.logoutUrl(); // get the logout url
}
}
</script>
You can totally use this without Vue. Configuration is pretty much the same.
// assets/js/symfony/index.ts
import { RouterSettings } from '@symfony-javascript/router/dist/router/RouterSettings';
import { Router } from '@symfony-javascript/router/dist/router/Router';
import { TranslatorSettings } from '@symfony-javascript/translator/dist/translator/TranslatorSettings';
import { Translator } from '@symfony-javascript/translator/dist/translator/Translator';
import messages from './messages.json';
import routes from './routes.json';
const debug = process.env.NODE_ENV !== 'production';
const RouterConfig: RouterSettings = {
data: routes,
forceCurrentScheme: true,
fallbackScheme: 'https',
debug,
};
const TranslatorConfig: TranslatorSettings = {
data: messages,
locale: 'fr',
strategy: 'strict',
debug,
};
export const translator = new Translator(TranslatorConfig);
export const router = new Router(RouterConfig);
// assets/js/scripts/some-file.js
import { translator, router } from '../symfony';
translator.trans('some_message', {}, 'messages', 'en'); // translate something
$router.path('some_route', {}); // same as above
$router.url('some_route', {}); // same as above, but full URL
$router.logoutPath(); // get the logout path
$router.logoutUrl(); // get the logout url
The Router
constructor can take an object containing its settings.
Property | Default Value | Possible Values | Description |
---|---|---|---|
data | {} | Any object containing a valid routing configuration | An object containing the routing configuration, exported from the bundle. |
debug | false | true | false | Enables debugging (actually does nothing for now) |
forceHttps | false | true | false | Forces HTTPS on generated links |
forceCurrentScheme | false | true | false | Force the current protocol on generated links. If yout current page is on http , all links will be http . |
fallbackScheme | https |
Any protocol | Will be the scheme if no scheme is provided in the routing configuration. |
fallbackBaseUrl | '' | Any URL | Will be the fallback base URL if none is provided in the routing configuration. |
fallbackHost | '' | Any host | Will be the fallback host if none is provided in the routing configuration. |
The Translator
constructor can take an object containing its settings.
Property | Default Value | Possible Values | Description |
---|---|---|---|
data | {} | Any object containing a valid translation configuration | An object containing the translation configuration, exported from the bundle. |
debug | false | true | false | Enables debugging (actually does nothing for now) |
locale | en |
Any language string | The default locale for translating. |
domain | domain |
Any domain | The default domain for translating. |
strategy | strict |
strict | fallback |
If strict , the translator will translate messages matching the exact domain and locale. If fallback , the translator will try to translate message from the domain and locale, and will fallback to the configured locales. |
pluralVariables | %count% , {{ count }} , {{count}} , $count |
An array of string | The variables the translator will look for in order to pluralize a translation. See below. |
pluralSeparator | ` | ` | Any character |
As you probably know, the Translator component of Symfony is able to pluralize. To do so, ou can pass a number variable that will determine which translation will be used.
For example, take this translation:
item_selected.precise: One item|$count items
item.selected: One item|Multiple items
translator.trans('item.selected.precise', { $count: 1 }); // Output: One item
translator.trans('item.selected.precise', { $count: 2 }); // Output: 2 items
translator.trans('item.selected', { $count: 2 }); // Output: Multiple items
The translator has a add
method that allows you to add messages to its catalogue at runtime.
translator.add('some_key', 'Some translated content', 'messages', 'en');
If you omit the domain or locale, the ones provided in your current configuration will be used.
The typing of the add
method is the following:
add(id: string, value: string, domain?: string, locale?: string): this;
There are two events available: messageNotFound
and translated
. I don't know why you would need them, but I felt like adding them anyway.
translator.on('messageNotFound', (data) => {
console.warn(`Message '${data.id}' does not exist in domain '${data.domain}' for locale '${data.locale}'.`);
}));
// the `.off` method exists as well
The data
variable is a MessageEventHandler
object, which has the following typing:
interface MessageEventHandler {
id?: string;
domain?: string;
locale?: string;
catalogue?: Catalogue;
variables?: Variables;
}
The libraries are written in TypeScript, so their types are available. As a reference, here is the router methods:
settings: RouterSettings;
collection: RouteCollection | undefined;
getCollection(): RouteCollection;
url(name: string, parameters?: any, schemeRelative?: boolean): string;
path(name: string, parameters?: any): string;
absoluteUrl(path: string, schemeRelative?: boolean): string;
logoutUrl(): string | null;
logoutPath(): string | null;
And the translator has the following:
settings: TranslatorSettings;
readonly catalogue: Catalogue;
add(id: string, value: string, domain?: string, locale?: string): this;
trans(id: string, variables?: Variables, domain?: string, locale?: string): string;
on(event: MessageEvents, handler: {
(data: MessageEventHandler): void;
}): void;
off(event: MessageEvents, handler: {
(data: MessageEventHandler): void;
}): void;