# Javascript Bundle <div align="center"> <b>Symfony bundle for front-end translation and routing</b>       </div> Installation ============ Applications that use Symfony Flex ---------------------------------- Open a command console, enter your project directory and execute: ```console $ composer require symfony-javascript/javascript-bundle ``` Applications that don't use Symfony Flex ---------------------------------------- ### Step 1: Downloading the Bundle Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle: ```console $ composer require symfony-javascript/javascript-bundle ``` This command requires you to have Composer installed globally, as explained in the [installation chapter](https://getcomposer.org/doc/00-intro.md) of the Composer documentation. ### Step 2: Enabling the Bundle Then, enable the bundle by adding it to the list of registered bundles in the `config/bundles.php` file of your project: ```php // config/bundles.php return [ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true], // ... SymfonyJavascript\JavascriptBundle\JavascriptBundle::class => ['all' => true], ]; ``` ### Step 3: Configuring the Bundle You can configure the bundle if the default values do not suit your project. I recommand changing the extract paths to your needs. ```yaml # 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 ``` Usage ===== Using the commands ------------------ The bundle provides two commands to extract your route and translation files. It also provides a command to extract them all. ```console $ 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. Working with Javascript ----------------------- ### Step 1: Installation You can chose to use whatever you want, but there are three usable libraries. There is a router, a translator, and a [Vue](vuejs/vu) adapter. Either install everything: ```console $ yarn add @symfony-javascript/vue-adapter @symfony-javascript/router @symfony-javascript/translator ``` Or only what you want ```console $ yarn add @symfony-javascript/vue-adapter $ yarn add @symfony-javascript/router $ yarn add @symfony-javascript/translator ``` ### Step 2: Configuration 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. ```yaml # config/packages/javascript.yaml javascript: translation: extract_path: assets/js/symfony/messages routing: extract_path: assets/js/symfony/routes ``` #### Using Vue You will then need to configure both the `router` and `translator`. Here is a good starter that you can use with minimal changes required. ```javascript // 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` and `router` 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. ```javascript // assets/js/main.ts import Vue from 'vue'; import RegisterSymfony from './symfony'; RegisterSymfony(Vue); // initialize your Vue below ``` ```javascript // 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> ``` #### Without Vue You can totally use this without Vue. Configuration is pretty much the same. ```javascript // 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); ``` ```javascript // 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 ``` #### Router Configuration 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. #### Translator 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](#translator-pluralization). | pluralSeparator | `|` | Any character | The separator for pluralization. Do not changed that unless you know what to do. Default Symfony language files don't need to change that. #### Translator Pluralization 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: ```yaml item_selected.precise: One item|$count items item.selected: One item|Multiple items ``` ```javascript 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 ``` #### Adding translation messages at runtime The translator has a `add` method that allows you to add messages to its catalogue at runtime. ```javascript 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: ```typescript add(id: string, value: string, domain?: string, locale?: string): this; ``` #### Translation events There are two events available: `messageNotFound` and `translated`. I don't know why you would need them, but I felt like adding them anyway. ```javascript 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: ```typescript interface MessageEventHandler { id?: string; domain?: string; locale?: string; catalogue?: Catalogue; variables?: Variables; } ``` ### Types The libraries are written in TypeScript, so their types are available. As a reference, here is the router methods: ```typescript 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: ```typescript 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; ```