PHP and Symfony
This tutorial will show you how to use the Lokalise PHP SDK to create webhooks, listen to webhook events in third-party apps, and handle incoming notifications.
In this tutorial you'll learn how to...
- Work with Lokalise API tokens
- Create webhooks with the Lokalise API
- Listen to webhooks events
- Handle incoming notifications
You can find the source code on GitHub.
Prerequisites
This guide assumes that you have a Lokalise project (if not, learn how to create your Lokalise project here). In this tutorial, we are going to upload English translations. Therefore, make sure that your project has the English language added with the “en” ISO code.
If you want to follow this guide locally on your computer, you need to have the following software installed:
- PHP 8.0.2 or above
- Symfony CLI tool
- Code editor
What we are going to build
We are going to create a simple application that will allow users to register webhooks in their Lokalise projects. The app will also listen to incoming notifications generated by these webhooks and react to them by sending API requests.
Preparing a new Symfony app
Create a new Symfony app by running the following command:
symfony new lokalise-webhooks --webapp
This will create the skeleton app in the lokalise-webhooks
folder for you.
Now go into that folder and set the Symfony app to use the Lokalise PHP SDK:
cd lokalise-webhooks
composer require lokalise/php-lokalise-api
Getting the API token
You should obtain an existing API token or generate a new one. Learn how to get a Lokalise API token. Store it inside the .env
file in the following way:
LOKALISE_TOKEN=123def456
Open config/services.yaml
and add token as a parameter:
parameters:
lokalise.api_token: '%env(LOKALISE_TOKEN)%'
Running your app
In order to follow this tutorial, you will need to deploy your app to some hosting. Also, you can use ngrok:
- Run the following command:
symfony server:start
- Follow the ngrok getting started guide
- In your terminal, navigate to the folder where you unzipped ngrok
- Run
./ngrok config add-authtoken <token>
- Finally, run
./ngrok http 800
ngrok (Ctrl+C to quit)
Session Status online
Account User Name (Plan: Free)
Version 3.0.3
Region Europe (eu)
Latency calculating...
Web Interface http://127.0.0.1:4040
Forwarding https://50e1-90-133-221-110.eu.ngrok.io -> http://localhost:8000
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00
Your app must be publicly accessible
If your Symfony app is not publicly accessible, it won't be possible to register a Lokalise webhook. That's because Lokalise sends a special "ping" request to the provided URL and expects to receive a 2xx status code. If the URL is not accessible, the webhook won't be created.
Adding template
Now let's add a new template to the templates/web_hooks/index.html.twig
file:
{% for message in app.flashes('notice') %}
<div><strong>{{ message }}</strong></div>
{% endfor %}
<br />
<form method="post" action="{{ path('app_hooks_register')}}">
<label>Enter Project ID: <input type="text" name="projectId" /></label><br /><br />
<input type="submit" value="Register webhook" />
</form>
We will use this form to enter a project ID and register a new webhook.
Registering a new webhook
Tweak the src/Controllers/WebHooksController.php
file to create a new webhook for the chosen project:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Annotation\Route;
class WebHooksController extends AbstractController
{
#[Route('/', name: 'app_hooks_home')]
public function index(SessionInterface $session): Response
{
return $this->render('web_hooks/index.html.twig');
}
#[Route('/registerHook', name: 'app_hooks_register', methods: ['POST'])]
public function registerWebhook(SessionInterface $session, Request $request): Response
{
$projectId = $request->request->get('projectId', null);
if (empty($projectId)) {
$this->addFlash('notice', 'Please enter a project id');
return $this->redirect($this->generateUrl('app_hooks_home'));
}
$client = new \Lokalise\LokaliseApiClient($this->getParameter('lokalise.api_token'));
$client->webhooks->create(
$projectId,
[
'url' => 'https://50e1-90-133-221-110.eu.ngrok.io/triggerHook',
'events' => [
'project.key.added',
],
]
);
$this->addFlash('notice', 'Hook registered');
return $this->redirect($this->generateUrl('app_hooks_home'));
}
This webhook will listen to the project.key.added
event. Notifications will be sent to the /triggerHook
route.
Responding to notifications
Now let's tweak the src/Controllers/WebHooksController.php
file again to add a new /triggerHook
route listening to all the incoming events:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Annotation\Route;
class WebHooksController extends AbstractController
{
#[Route('/triggerHook', name: 'app_hooks_trigger', methods: ['POST'])]
public function triggerHook(Request $request): Response
{
$data = json_decode($request->getContent(), true, 5, JSON_THROW_ON_ERROR);
$event = $data['event'] ?? null;
if ($event === 'project.key.added') {
$keyId = $data['key']['id'] ?? null;
$projectId = $data['project']['id'] ?? null;
if ($projectId && $keyId) {
$client = new \Lokalise\LokaliseApiClient($this->getParameter('lokalise.api_token'));
$client->comments->create($projectId, $keyId, [
'comments' => [
[
'comment' => '@Bob please review this new key ',
],
]
]);
$client->keys->update($projectId, $keyId, [
'is_hidden' => true
]);
}
}
return new Response('ok');
}
}
Webhook data is sent in JSON format. We make sure that the received notification is not a ping and has a proper event name. Then, we just add a key comment and update the key using event's data.
You can introduce an additional level of security by checking the request headers. Specifically, each reqest with contain a special "secret" (that you can customize when creating a webhook) as well as the Lokalise project ID and the webhook ID. Please find more information in the corresponding document.
A notice on notification handlers
Please note that your event handlers must respond to POST requests and return 2xx status codes. Otherwise, Lokalise will consider the webhook notification to be unsuccessful and will try to re-send failed notifications multiple times.
Testing it out
Now everything is ready! Open your app and enter a project ID to register a new webhook. Open Lokalise, proceed to your project and click "Apps" in the top menu. Find the "Webhooks" app, click on it, and then press "Manage". You'll see that a new webhook was registered for you:
Note the "X-Secret header" hidden value. You can use this value inside your app and compare it with the one sent to the notify
route for extra protection (thus filtering out malicious requests).
Now return to the Lokalise project editor and create a new translation key. Reload the page and make sure the key was hidden:
Click on the "Comments" button (the first button on the screen above) and make sure the comment is displayed properly:
That's it, great job!
Webhooks and bulk actions
Webhooks support Lokalise bulk actions as well as the find/replace operation. You can find more info the Webhooks article but let's see how to listen to such events.
First of all, it's important to enable the proper events in the webhooks configuration. For instance, if you'd like to monitor find/replace operation as well as applying pseudolocalization using bulk actions, you should tick the translation updated event:
Please keep in mind that as long as the bulk actions may involve multiple keys, the event payload will contain data from 1 and up to 300 keys. If more than 300 keys were involved in the operation, you'll receive multiple events.
Also note that in case of bulk actions, the event name will be pluralized: project.translations.updated
, not project.translation.updated
. For example, if you would like to display information about all the updated translations as well as project and user name, you can use the following code snippet:
#[Route('/triggerHook', name: 'app_hooks_trigger', methods: ['POST'])]
public function triggerHook(Request $request): Response
{
$data = json_decode($request->getContent(), true, 5, JSON_THROW_ON_ERROR);
$event = $data['event'] ?? null;
if ($event === 'project.translations.updated') {
echo "Project name: {$data['project']['name']}";
echo "User name: {$data['user']['full_name']}";
foreach ($data['translations'] as $translation) {
echo "Translation ID: {$translation['id']}";
echo "Translation value: {$translation['value']}";
echo "Language: {$translation['language']['name']}";
echo "Key: {$translation['key']['name']}";
}
}
return new Response('ok');
}
So, 'translations'
contain an array with all the translations data, including the language and key details.
Updated about 2 months ago