Les données en temps réel sont aujourd’hui l’un des piliers essentiels des applications modernes. Avoir un système capable d'envoyer des informations bidirectionnelles nous permet de rester à jour avec une variété d'informations. De tels exemples peuvent inclure des applications de messagerie, des analyses de données pour les tableaux de bord utilisés pour les données financières, des affichages tête haute (HUD) utilisés dans des applications augmentées et virtuelles. Comme un pilote de chasse pilotant un avion ou un consommateur occasionnel utilisant un casque Apple Vision Pro. Il existe une infinité de cas d'utilisation pour cette technologie.
Quand on parle d'applications générales de messagerie instantanée, la possibilité de parler à quelqu'un en temps réel ouvre la porte à de nombreuses possibilités uniques. Notre monde est devenu de plus en plus connecté grâce à ces nouvelles capacités. Dans l'article d'aujourd'hui, nous apprendrons tout sur la messagerie en créant une application de messagerie en temps réel. L'application pourra se connecter à deux plateformes d'applications en temps réel différentes qui sont Pusher et PubNub.
Notre application sera assez similaire à une version de base simple des applications WhatsApp et Telegram. Il sera possible d'ouvrir plusieurs navigateurs Web ou onglets de navigateur, ce qui nous donnera le pouvoir de nous connecter avec plusieurs utilisateurs. De cette façon, nous pouvons tester le chat de groupe entre plusieurs utilisateurs, comme vous le feriez s'il s'agissait d'une véritable application de chat.
Vous pouvez voir ci-dessous à quoi ressemblera notre tableau de bord principal, car vous pouvez voir qu'il y a des boutons pour accéder aux deux versions de notre application de messagerie :
Et ici, vous pouvez voir à quoi ressemble la version PubNub de notre application dans cette capture d'écran. Il est représenté par un en-tête bleu :
La version Pusher a un en-tête vert et avec cela vous pourrez faire visuellement la différence entre les deux :
Très bien, notre introduction rapide étant terminée, nous pouvons maintenant jeter un regard beaucoup plus approfondi en voyant comment ils contrastent les uns avec les autres. La base de code de l'application de messagerie peut être trouvée sur mon GitHub ici : https://github.com/andrewbaisden/realtime-chat-app.
Avant de commencer, jetez un œil à ces conditions préalables et assurez-vous que tout est configuré avant de commencer.
Les deux plates-formes partagent beaucoup de points communs, mais il existe quelques différences notables qui les rendent faciles à distinguer. L'un des principaux est le fait que leur architecture n'est pas la même.
Dans le cas de PubNub, il s'agit d'une plateforme cloud qui peut prendre en charge le streaming des données et des messages. La latence est très faible et le service est disponible dans le monde entier. La mise à l'échelle et l'infrastructure sont bien gérées par la plate-forme, les développeurs sont donc libres de travailler sur ce qui compte, à savoir les projets.
Maintenant, avec Pusher, ils proposent différentes options de déploiement qui sont auto-hébergées et hébergées dans le cloud. Lorsque vous utilisez l'auto-hébergé, Pusher est capable de fonctionner sur votre propre matériel ou logiciel personnalisé, ce qui vous donne beaucoup de liberté. En ce qui concerne la solution hébergée dans le cloud, vous pouvez vous attendre à un service similaire à celui de PubNub.
Lorsque nous comparons leurs fonctionnalités, nous pouvons voir qu'elles offrent toutes deux des SDK et des bibliothèques qui sont prises en charge par de nombreux langages de programmation. Il s'agit notamment de JavaScript, Python, Java, Swift, Ruby et bien d'autres. Les chaînes sont disponibles sur les deux plates-formes, ce qui nous permet de publier et de souscrire à différents flux de données. La présence est une autre option qui nous permet de voir le statut en ligne et hors ligne de tous les utilisateurs en temps réel sur les différents canaux que nous configurons. En ce qui concerne l'historique des messages, c'est une histoire similaire et il en va de même pour les notifications push.
Un autre aspect intéressant est le fait qu'ils sont riches en fonctionnalités et que la documentation est très complète, ce qui rend très facile l'apprentissage et la mise à jour des différentes bizarreries proposées. Vous pouvez retrouver leur documentation respective ci-dessous :
Documentation PubNub : PubNub
Documentation du poussoir : Pousseur
Il est temps de travailler sur notre application ! Assurez-vous d'abord que vous avez un compte sur PubNub et Pusher, nous allons maintenant passer rapidement par le processus de création d'un compte sur les deux plateformes, alors suivez-nous si vous n'avez pas encore créé de compte.
En commençant par PubNub, accédez à la page d'accueil du site Web et cliquez sur le bouton en haut à droite pour essayer gratuitement comme indiqué ci-dessous.
Vous devriez maintenant voir la page d'inscription, alors n'hésitez pas et utilisez le formulaire pour créer votre compte.
Ok maintenant, avec votre compte créé, l'accès au tableau de bord devrait être possible. Utilisez le menu pour accéder à la section Keysets, puis recherchez votre application et son keyset. C'est également la partie où vous devez créer une application s'il n'y en a pas déjà une.
Vos clés API devraient être visibles maintenant, alors assurez-vous que les options de présence et de persistance sont activées, car nous en avons besoin pour suivre les utilisateurs et les données.
Bon, nous en avons terminé avec notre compte PubNub pour l'instant, travaillons sur notre compte Pusher. Comme avant, allez sur le site Web et cliquez sur le bouton d'inscription dans le coin supérieur droit de la page.
Maintenant, sur l'écran suivant, utilisez le formulaire pour créer un compte comme vous le voyez ici.
Une chaîne doit être créée, donc sur la page du tableau de bord, sélectionnez le bouton Gérer pour le faire.
Sur la page suivante, qui devrait être l'écran de la chaîne, créez une application en utilisant le bouton Créer une application, comme indiqué dans cette capture d'écran.
Nous pouvons maintenant passer au formulaire et comme vous pouvez le voir dans cet exemple, la configuration doit être adaptée à votre cas d'utilisation.
Donc, une fois cette étape terminée, la page de la chaîne devrait s'afficher telle qu'elle est ici.
Cliquez sur les clés d'application qui se trouvent dans le menu de la barre latérale et vous aurez ensuite accès aux clés API dont vous aurez besoin plus tard.
Super, la phase de configuration du compte est maintenant terminée. Dans la section suivante, nous allons commencer par le code de notre application. La première étape consistera à créer l'architecture du projet avec les dossiers, etc. J'ai créé un script copier-coller pour la ligne de commande afin que nous n'ayons pas à écrire toutes les commandes manuellement. Commencez par créer un projet sur votre ordinateur appelé realtime-chat-app puis accédez à l'emplacement à l'aide de la ligne de commande.
Utilisez Next.js pour créer un projet, puis utilisez le script shell pour configurer le projet.
Nous commençons par exécuter cette commande pour créer un projet Next.js :
npx create-next-app client
Maintenant, sur l'écran de configuration, il est important de choisir Tailwind CSS et App Router car nous avons besoin de ces options dans ce projet.
Voici le script shell pour créer les fichiers et les dossiers et installer les dépendances, exécutez-le simplement dans le même dossier, il sera automatiquement inséré dans le dossier client. Si vous êtes déjà dans le dossier client, vous pouvez omettre la première ligne du script.
cd client npm install @pubnub/react-chat-components axios pubnub pubnub-react pusher pusher-js touch .env.local cd src/app mkdir components pubnub pusher touch components/ChatInterface.js components/ChatMessage.js components/ChatPubNub.js components/ChatPusher.js components/DashboardButton.js components/Header.js components/UserLogin.js pubnub/page.js pusher/page.js cd ../../.. mkdir server cd server npm init -y npm install express cors pusher dotenv touch index.js .env cd ..
Il se passe beaucoup de choses ici, alors voyons ce que fait ce script :
Jetez un oeil à cette capture d'écran, voici à quoi devrait ressembler notre projet dans votre IDE :
The hard work is done that build script did most of the work we just have to add the code to the files.
Right our first file is the globals.css file so clear all the code in that file and replace it with what is shown here:
@tailwind base; @tailwind components; @tailwind utilities; body { background-color: rgb(15 23 42); }
Code cleanup has been done in this file and now we have a background colour for our application.
Onto the layout.css file. Continuing from what we just did replace all the code with this new code:
import { Ubuntu } from 'next/font/google'; import './globals.css'; const ubuntu = Ubuntu({ subsets: ['latin'], weight: ['300', '400', '500', '700'], }); export const metadata = { title: 'Realtime Chat App', description: 'Generated by create next app', }; export default function RootLayout({ children }) { return ( <html lang="en"> <body className={ubuntu.className}>{children}</body> </html> ); }
Ubuntu is now the default font in our application.
All thats left in this section is to replace all of the code in our next.config.mjs file with this code:
/** @type {import('next').NextConfig} */ const nextConfig = { images: { remotePatterns: [ { hostname: 'res.cloudinary.com', }, ], }, }; export default nextConfig;
Cloudinary was added as an image host so now we can use it throughout our app.
And with that the project setup phase is done so in the next section we will work on the main files for our codebase. First we will do PubNub and then we will do Pusher.
The initilisation and configuration part will be the first one to tackle. You must sign into your PubNub account, and find the application you made at the start. Locate your Keysets as well as the Publish and Subscribe Keys. Now put them inside of the .env.local file in the project.
Here is an example of where they should be put:
NEXT_PUBLIC_PUBNUB_PUBLISH_KEY=your-publish-key NEXT_PUBLIC_PUBNUB_SUBSCRIBE_KEY=your-subscribe-key
Time to add some code to our components/ChatPubNub.js file, and this is the file where we can find the main code for subscribing to channels, handling the presence and publishing our message.
Put the code you see here into the components/ChatPubNub.js file:
import { useState, useEffect, useRef } from 'react'; import PubNub from 'pubnub'; import ChatInterface from './ChatInterface'; export default function ChatPubNub({ activeUser }) { const [chats, setChats] = useState([]); const [count, setCount] = useState(1); const bottomRef = useRef(null); let pubnub; const channelName = 'presence-chatroom'; useEffect(() => { pubnub = new PubNub({ publishKey: process.env.NEXT_PUBLIC_PUBNUB_PUBLISH_KEY, subscribeKey: process.env.NEXT_PUBLIC_PUBNUB_SUBSCRIBE_KEY, uuid: activeUser, }); pubnub.addListener({ message: (messageEvent) => { const chat = messageEvent.message; setChats((prevChats) => [...prevChats, chat]); }, }); const presenceChannelName = `${channelName}-pnpres`; pubnub.subscribe({ channels: [channelName], withPresence: true, presenceChannels: [presenceChannelName], }); const scrollToBottom = () => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }; scrollToBottom(); return () => { pubnub.unsubscribeAll(); }; }, [chats, activeUser, count]); const handleKeyUp = (evt) => { const value = evt.target.value; if (evt.keyCode === 13 && !evt.shiftKey) { const chat = { user: activeUser, message: value, timestamp: +new Date() }; evt.target.value = ''; pubnub.publish({ channel: channelName, message: chat, }); } }; return activeUser ? ( <> <ChatInterface activeUser={activeUser} count={count} chats={chats} handleKeyUp={handleKeyUp} bottomRef={bottomRef} /> </> ) : null; }
Here is a quick explanation of what our code does in this file. We can do subscriptions to channels, publish messages to different channels as well as handle the overall presence information. With these capabilities our app will function a lot like a group chat and with presence switched on we can see how many users are online in real-time.
We can work on our next file now which is the components/ChatInterface.js so add this code to it:
import ChatMessage from './ChatMessage'; export default function ChatInterface({ activeUser, count, chats, handleKeyUp, bottomRef, }) { return ( <div className="flex"> <aside className="w-64 min-w-64 bg-slate-800 p-2"> <h1 className="text-lg text-white">Chats</h1> <div className="flex justify-between mt-2"> <div className="flex"> <div className="rounded-full bg-slate-100 h-10 w-10 mr-2"></div> <div className="text-sm"> <p className="text-white">John</p> <p className="text-slate-400">Hello world</p> </div> </div> <div className="text-sm text-slate-400">Friday</div> </div> </aside> <section className="grow"> <div className="bg-zinc-800 p-2"> <div className="flex"> <div className="rounded-full bg-slate-100 h-10 w-10 mr-2"></div> <div> <h1 className="text-2xl text-white">{activeUser}</h1> <span className="text-gray-300">users online: {count}</span> </div> </div> </div> <div className="bg-zinc-900 p-2 overflow-y-auto h-80 max-h-80 text-white"> {chats.map((chat, index) => { const previous = Math.max(0, index - 1); const previousChat = chats[previous]; const position = chat.user === activeUser ? 'right' : 'left'; const isFirst = previous === index; const inSequence = chat.user === previousChat.user; const hasDelay = Math.ceil( (chat.timestamp - previousChat.timestamp) / (1000 * 60) ) > 1; return ( <div key={index}> {(isFirst || !inSequence || hasDelay) && ( <div> <span>{chat.user || 'Anonymous'}</span> </div> )} <ChatMessage message={chat.message} position={position} /> </div> ); })} <div ref={bottomRef} />{' '} </div> <div className="w-full bg-zinc-800 p-2"> <textarea onKeyUp={handleKeyUp} placeholder="Enter a message" className="w-full block rounded mt-2 mb-2 p-2 text-white bg-zinc-600" ></textarea> </div> </section> </div> ); }
Our component displays the UI for our messaging chat interface. There is a section for messaging, sending messages and a sidebar, which could hold users when they are in the group.
The next component is for the components/ChatMessage.js file and this has our chat message interface.
Add this code to the file:
export default function ChatMessage({ message }) { return ( <div> <div className="mt-4 mb-4"> <span className="bg-zinc-600 p-2 rounded">{message}</span> </div> </div> ); }
Chat bubbles should become possible thanks to this component whenever we use the chat to send messages to users.
Dashboard buttons is what we require next so add this code to our components/DashboardButton.js file:
import Link from 'next/link'; import Image from 'next/image'; export default function DashboardButton({ url, img, alt }) { return ( <> <Link href={url}> <div className="rounded mr-4 bg-slate-50 hover:bg-slate-200 h-96 w-96 text-center flex items-center justify-center drop-shadow-lg uppercase"> <Image src={img} height={200} width={200} alt={alt} /> </div> </Link> </> ); }
We can now easily navigate between the PubNub and Pusher versions of our real-time messaging chat app using these reusable buttons.
Ok the navigation component is next and this is for our main header. Put this code in our file at components/Header.js:
import Link from 'next/link'; export default function Header() { return ( <> <nav className="bg-white flex justify-around p-8 mb-4 font-bold"> <Link href={'/'}>Dashboard</Link> <Link href={'/pusher'}>Pusher Chat App</Link> <Link href={'/pubnub'}>PubNub Chat App</Link> </nav> </> ); }
All our page routes are easily able to be navigated using this header component which has page links.
The login screen is next and this is the code our file at components/UserLogin.js desires:
import { useState } from 'react'; import ChatPubNub from '../components/ChatPubNub'; import ChatPusher from '../components/ChatPusher'; export default function UserLogin({ bgColor, appName }) { const [user, setUser] = useState(null); const handleKeyUp = (evt) => { if (evt.keyCode === 13) { const newUser = evt.target.value; setUser(newUser); } }; return ( <> <main> <div> <section> <div className={`p-4 ${bgColor} text-slate-100`}> <span> {user ? ( <span className="flex justify-between text-white"> <span> {user} <span>is online</span> </span> <span>{appName}</span> </span> ) : ( <span className="text-2xl text-white"> What is your name? </span> )} </span> {!user && ( <input type="text" onKeyUp={handleKeyUp} autoComplete="off" className="w-full block rounded mt-2 mb-2 p-2 text-black" /> )} </div> </section> {appName === 'PubNub Chat' ? ( <section>{user && <ChatPubNub activeUser={user} />}</section> ) : ( <section>{user && <ChatPusher activeUser={user} />}</section> )} </div> </main> </> ); }
Its a pretty straightforward login screen component whereby a user can choose a name and then they get redirected to the messaging app. There is logic to check which app a user is using and it automatically sends the user to the right chat interface for that version of the app.
Lets do the pubnub/page.js route file now and add this code to it:
'use client'; import Header from '../components/Header'; import UserLogin from '../components/UserLogin'; export default function PubNub() { return ( <div> <Header /> <UserLogin bgColor={'bg-sky-800'} appName={'PubNub Chat'} /> </div> ); }
We can find the main page route for the PubNub version messaging app.
Lastly we must add code to our page.js file in the root folder to complete our application so like before just replace the code with what we have written here:
'use client'; import DashboardButton from './components/DashboardButton'; export default function Home() { return ( <> <div className="h-screen flex justify-center items-center"> <div className="text-center"> <h1 className="mb-4 text-white text-4xl">Choose a messaging app</h1> <div className="grid gap-2 lg:grid-cols-2 md:grid-cols-1 sm:grid-cols-1"> <DashboardButton url={'/pusher'} img={ 'https://res.cloudinary.com/d74fh3kw/image/upload/v1715187329/pusher-logo_u0gljx.svg' } alt="Pusher Logo" /> <DashboardButton url={'/pubnub'} img={ 'https://res.cloudinary.com/d74fh3kw/image/upload/v1715189173/pubnub-logo-vector_olhbio.png' } alt="PubNub Logo" /> </div> </div> </div> </> ); }
Our main dashboard link can be found here which has the buttons for our PubNub and Pusher version of our application.
The PubNub messaging part of our application should be good to go now! Just cd into the client folder if you have not done so already and start the application with the usual Next.js run command:
npm run dev
Its worth mentioning that the Pusher part of our application is not going to work yet as we must complete the integrations in the upcoming section. To use the PubNub app go to the login screen, enter a name and hit the enter button and then you will see the messenger chat application screen. You can see your online status and the sidebar has a hard-coded user which is just an example.
To make the application more interactive open more browser tabs or browser windows and sign in with more users. Having a real-time group chat is now possible just like any other messaging app you are familiar with.
In the next section we shall get Pusher up and working.
This section will take less time because we get to reuse a lot of the components we used in the earlier sections. The difference this time around is that Pusher will need to connect to our backend server to work.
Like before we are going to start with the configuration files for our .env.local and .env files in the server and client folders. We need to add the same secrets to the files. Find your application on the Pusher platform and then find the App keys.
The App keys must be added to the env files with the right variables. Take a note of this key difference. Our client .env.local env file must have NEXT_PUBLIC at the start, and the .env file in the server folder does not require it and you can see that in the examples below.
Here is our .env.local file which is in the client folder:
NEXT_PUBLIC_PUBNUB_PUBLISH_KEY=your-publish-key NEXT_PUBLIC_PUBNUB_SUBSCRIBE_KEY=your-subscribe-key NEXT_PUBLIC_PUSHER_APP_ID=your-app-id NEXT_PUBLIC_PUSHER_APP_KEY=your-app-key NEXT_PUBLIC_PUSHER_APP_SECRET=your-app-secret NEXT_PUBLIC_PUSHER_APP_CLUSTER=your-cluster
And this is the .env file which can be found in the server folder:
PUSHER_APP_ID=your-app-id PUSHER_APP_KEY=your-app-key PUSHER_APP_SECRET=your-app-secret PUSHER_APP_CLUSTER=your-cluster
Time to work on the frontend so you know the drill add this code to the components/ChatPusher.js file:
import { useState, useEffect, useRef } from 'react'; import axios from 'axios'; import Pusher from 'pusher-js'; import ChatInterface from './ChatInterface'; export default function ChatPusher({ activeUser }) { const [chats, setChats] = useState([]); const [count, setCount] = useState(0); const bottomRef = useRef(null); let channel; let pusher; useEffect(() => { pusher = new Pusher(process.env.NEXT_PUBLIC_PUSHER_APP_KEY, { cluster: process.env.NEXT_PUBLIC_PUSHER_APP_CLUSTER, useTLS: true, channelAuthorization: { endpoint: 'http://localhost:8000/auth', }, }); channel = pusher.subscribe('presence-chatroom'); channel.bind('new-message', ({ chat = null }) => { if (chat) { setChats((prevChats) => [...prevChats, chat]); } }); channel.bind('pusher:subscription_succeeded', () => { updateMemberCount(channel); }); channel.bind('pusher:member_added', () => { updateMemberCount(channel); }); channel.bind('pusher:member_removed', () => { updateMemberCount(channel); }); const scrollToBottom = () => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }; scrollToBottom(); return () => { pusher.disconnect(); }; }, [chats]); const updateMemberCount = (presenceChannel) => { const memberCount = Object.keys(presenceChannel.members.members).length; console.log('Count people online', memberCount); setCount(memberCount); }; const handleKeyUp = (evt) => { const value = evt.target.value; if (evt.keyCode === 13 && !evt.shiftKey) { const chat = { user: activeUser, message: value, timestamp: +new Date() }; evt.target.value = ''; axios.post('http://localhost:8000/message', chat); } }; return activeUser ? ( <> <ChatInterface activeUser={activeUser} count={count} chats={chats} handleKeyUp={handleKeyUp} bottomRef={bottomRef} /> </> ) : null; }
There are similarities here to our PubNub component which matches this. We can subscribe to the channels, publish different messages and handle the presence. Although all of this is now done via the backend which now also works with basic authentication. Unlike the PubNub version however this version can accurately see how many users are online as well as when users are active, join and leave the group chat.
An authentication route is present for authenticating users and we incorporated a message route for posting all messages to our server.
Our frontend is almost completed just one file remains so add this code to our pusher.page.js file now:
'use client'; import Header from '../components/Header'; import UserLogin from '../components/UserLogin'; export default function Pusher() { return ( <div> <Header /> <UserLogin bgColor={'bg-emerald-800 '} appName={'Pusher Chat'} /> </div> ); }
This file ensures that our Pusher version has a working route. All thats left is to get the messaging app up and running when we do the server file next.
Before we do that we should setup our run script in our package.json file so add this script to it:
"scripts": { "start": "node index.js" },
Alright, last file! Add this code to our index.js file in the server folder so we can complete the backend:
const cors = require('cors'); const Pusher = require('pusher'); const express = require('express'); require('dotenv').config(); const crypto = require('crypto'); const dev = process.env.NODE_ENV !== 'production'; const port = process.env.PORT || 8000; const pusher = new Pusher({ appId: process.env.PUSHER_APP_ID, key: process.env.PUSHER_APP_KEY, secret: process.env.PUSHER_APP_SECRET, cluster: process.env.PUSHER_APP_CLUSTER, useTLS: true, }); const server = express(); server.use(cors()); server.use(express.json()); server.use(express.urlencoded({ extended: false })); const chatHistory = { messages: [] }; server.post('/message', (req, res) => { const { user = null, message = '', timestamp = +new Date() } = req.body; const chat = { user, message, timestamp }; chatHistory.messages.push(chat); pusher.trigger('presence-chatroom', 'new-message', { chat }); res.status(200).send('Message sent successfully.'); }); server.post('/messages', (req, res) => { res.json({ ...chatHistory, status: 'success' }); }); server.post('/auth', (req, res) => { const socketId = req.body.socket_id; const channel = req.body.channel_name; console.log('Socket and channel', socketId, channel); const userId = crypto.randomBytes(8).toString('hex'); const presenceData = { user_id: userId, user_info: { name: 'Anonymous User', }, }; const auth = pusher.authorizeChannel(socketId, channel, presenceData); res.send(auth); }); server.listen(port, (err) => { if (err) throw err; console.log(`Server is running on port: ${port} http://localhost:${port}`); });
Just a quick run through of this file so we can understand how it works. If you don't know its an Express server file which can connect to the Pusher API and it has routes for the authentication, message posting, in addition to getting chat history for all messages.
For connectivity, cors is implemented, so we don't get any of those annoying errors when trying to connect to different servers. The crypto module is used to doing various tasks like hash generation and encrypting and decrypting data.
With our codebase at MVP status, all you have to do is run the backend server in a different terminal window with the following command as shown below:
npm run start
So our server will run on port 8000 and you can change this in the server code if need be. Of course our Next.js application runs on port 3000, they need to be on different ports for obvious reasons. You already know how to use the PubNub version, the Pusher version works much the same.
Congratulations you have reached the end of this tutorial and created a working real-time messaging application!
Thats it we have completed the tutorial, learned about both real-time messaging applications and built a working demo application. As you have learned both platforms offer a similar feature set although Pusher has self-hosted and cloud options whereas PubNub offers only the later.
Ultimately your choice of platform will come down to what you make of their pros and cons. They have a free plan, so testing them is pretty easy to do. Pusher has flexible pricing in contrast to PubNubs strict pricing that offers much cheaper starter options priced at $98 compared to $49 for the Pusher startup option.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!