Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

Vous n'avez pas encore de compte Developpez.com ? L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Developpez.com

Qt

Choisissez la catégorie, puis la rubrique :

Viadeo Twitter Facebook Share on Google+   
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Internationalisation avec Qt

L'internationalisation d'une application est le processus ayant pour but de rendre l'application utilisable en dehors de son propre pays.

Classes et API Qt impliquées

Ces classes permettent l'internationalisation des applications Qt.

QInputContext Abstraction des données et états de composition spécifiques aux méthodes de saisie.
QLocale Conversions entre des nombres et leur représentation en chaînes de caractères dans diverses langues.
QSystemLocale Peut être utilisé pour des ajustements fins des options régionales de l'utilisateur.
QTextCodec Conversions entre encodages de texte.
QTextDecoder Décodeur.
QTextEncoder Encodeur.
QTranslator Gestion de l'internationalisation des textes affichés.
Règles de traduction pour les pluriels Un résumé des règles de traduction pour les pluriels produites par les outils d'internationalisation (i18n) de Qt.
Écrire du code source pour la traduction La façon d'écrire du code source rendant possible la traduction des textes visibles par l'utilisateur.

Langues et systèmes d'écriture

Dans certains cas, l'internationalisation est simple ; par exemple, rendre une application destinée aux USA accessible aux utilisateurs australiens ou anglais peut ne demander que quelques corrections orthographiques. Mais rendre la même application utilisable par des japonais ou rendre une application coréenne utilisable par des allemands va demander au logiciel de non seulement opérer dans des langues différentes, mais aussi d'utiliser des techniques de saisie et des conventions de présentation différentes et différents encodages de caractères.

Qt tente de rendre l'internationalisation la moins pénible possible pour les développeurs. Tous les widgets de saisie et les méthodes de dessin de texte dans Qt intègrent la gestion de toutes les langues supportées. Le moteur de polices intégré est capable de simultanément produire un rendu correct de textes contenant des caractères de systèmes d'écriture différents.

Qt gère la plupart des langues utilisées aujourd'hui, en particulier :

  • les langues est-asiatiques (chinois, japonais et coréen) ;
  • les langues occidentales (utilisant l'écriture latine) ;
  • l'arabe ;
  • les langues cyrilliques (russe, ukrainien, etc.) ;
  • le grec ;
  • l'hébreu ;
  • le thaï et le laotien ;
  • tous les scripts en Unicode 5.1 ne demandant pas de traitement particulier.

Sur Windows, Unix/X11 avec FontConfig (gestion des polices côté client) et Qt for Embedded Linux, les langues suivantes sont également supportées :

  • bengali ;
  • dévanagari ;
  • divehi (alphabet thâna) ;
  • gujarâtî ;
  • gurmukhi ;
  • kannada ;
  • khmer ;
  • malayâlam ;
  • birman ;
  • syriaque ;
  • tamil ;
  • télougou ;
  • tibétain ;
  • n'ko.

Plusieurs de ces systèmes d'écriture possèdent des caractéristiques particulières.

  • Comportement spécial des sauts de ligne. Certaines langues asiatiques sont écrites sans espaces entre les mots. Les sauts des lignes peuvent se produire soit après chaque caractère (avec des exceptions) comme en chinois, japonais et coréen ou sur des frontières logiques des mots comme en thaï.
  • Écriture bidirectionnelle. L'arabe et l'hébreu sont écrits de droite à gauche, sauf pour les nombres et les fragments de texte en Anglais qui sont écrits de gauche à droite. Le comportement exact est décrit dans l'annexe technique Unicode #9.
  • Marques diacritiques (accents ou umlauts dans les langues européennes). Certaines langues comme le vietnamien font un usage intensif de ces marques et certains caractères peuvent posséder simultanément plusieurs marques pour clarifier la prononciation.
  • Ligatures. Dans certains contextes, des paires de caractères sont remplacées par un glyphe combiné formant une ligature. Les ligatures fl et fi utilisées dans les livres européens et américains sont les exemples les plus courants.

Qt essaie de traiter tous les cas listés ci-dessus. Vous ne devez normalement pas vous soucier de ces particularités tant que vous utilisez les widgets de saisie Qt (par exemple, QLineEdit, QTextEdit et leur classes dérivées) et les widgets d'affichage de Qt (comme QLabel).

La gestion de ces systèmes d'écriture est transparente pour le programmeur et complètement encapsulée dans le moteur texte de Qt. Cela signifie que vous n'avez pas besoin de connaître le système d'écriture utilisé dans une langue particulière, sauf pour les points mineurs suivants :

  • QPainter::drawText(int x, int y, const QString &str) dessinera toujours la chaîne avec un bord gauche à la position spécifiée par les paramètres x et y. Ceci donnera le plus souvent un texte aligné à gauche. Les chaînes en arabe et en hébreu sont généralement alignées à droite, donc, pour ces langues, il faut utiliser la version de drawText() prenant en paramètre un QRect pour obtenir un alignement en fonction de la langue ;
  • lorsque vous écrivez vous propres contrôles de saisie de texte, utilisez QTextLayout. Dans certaines langues (par exemple l'arabe ou les langues du sous-continent indien), la largeur et la forme du glyphe changent en fonction des caractères voisins, ce qui est pris en compte par QTextLayout. L'écriture d'un contrôle de saisie requiert souvent une connaissance du contexte dans lequel il va être utilisé. Le plus simple est généralement de dériver QLineEdit ou QTextEdit.

Les sections suivantes donnent des informations sur l'état du support de l'internationalisation (i18n) dans Qt. Voir aussi le manuel de Qt Linguist.

Pas à pas

L'écriture de logiciel international et multi-plateforme avec Qt est un processus simple et incrémental. Votre logiciel peut être internationalisé en suivant les étapes suivantes.

Utiliser QString pour tous les textes visibles par l'utilisateur

Étant donné que QString utilise en interne l'encodage Unicode 5.1, toutes les langues du monde peuvent être traitées de façon transparente en utilisant les opérations familières de manipulation de texte. De plus, comme toutes les fonctions Qt présentant du texte à l'utilisateur prennent un paramètre QString, il n'y a pas de surcoût lié à la conversion de char * en QString.

Les chaînes qui sont dans le « domaine du programmeur » (comme les noms de QObject et les textes des formats de fichier) n'ont pas besoin d'utiliser QString ; le char * traditionnel ou la classe QByteArray suffit.

Vous ne devriez pas remarquer de différence du fait de l'usage de l'Unicode ; QString et QChar se comportent comme des versions plus faciles à utiliser des const char * et char du C traditionnel.

Partout où votre programme utilise du « texte entre guillemets » pour du texte qui sera présenté à l'utilisateur, assurez-vous qu'il sera traité par la fonction QCoreApplication::translate(). Pour l'essentiel, il suffit d'utiliser QObject::tr(). Par exemple, en supposant que

LoginWidget

est une classe dérivée de QWidget :

 LoginWidget::LoginWidget()
 {
     QLabel *label = new QLabel(tr("Mot de passe:"));
     ...
 }

Ce cas représente 99 % des chaînes visibles par l'utilisateur que vous écrirez.

Si le texte n'est pas dans une fonction membre d'une classe dérivée de QObject, utilisez soit la fonction tr() de la classe appropriée, soit directement la fonction QCoreApplication::translate():

 void some_global_function(LoginWidget *logwid)
 {
     QLabel *label = new QLabel(
                 LoginWidget::tr("Mot de passe:"), logwid);
 }
 
 void same_global_function(LoginWidget *logwid)
 {
     QLabel *label = new QLabel(
                 qApp->translate("LoginWidget", "Mot de passe:"), logwid);
 }

Si vous devez traduire du texte complètement à l'extérieur d'une fonction, deux macros peuvent vous aider : QT_TR_NOOP() et QT_TRANSLATE_NOOP(). Elles marquent simplement le texte pour une extraction par l'utilitaire lupdate décrit ci-dessous. Les macros ne conservent que le texte (sans le contexte).

Exemple de QT_TR_NOOP() :

 QString FriendlyConversation::greeting(int type)
 {
     static const char *greeting_strings[] = {
         QT_TR_NOOP("Bonjour"),
         QT_TR_NOOP("Au revoir")
     };
     return tr(greeting_strings[type]);
 }

Exemple de QT_TRANSLATE_NOOP() :

 static const char *greeting_strings[] = {
     QT_TRANSLATE_NOOP("FriendlyConversation", "Bonjour"),
     QT_TRANSLATE_NOOP("FriendlyConversation", "Au revoir")
 };
 
 QString FriendlyConversation::greeting(int type)
 {
     return tr(greeting_strings[type]);
 }
 
 QString global_greeting(int type)
 {
     return qApp->translate("FriendlyConversation",
                            greeting_strings[type]);
 }

Si vous désactivez la conversion automatique de const char * en QString en compilant votre logiciel avec la macro QT_NO_CAST_FROM_ASCII définie, vous aurez de fortes chances de détecter des chaînes que vous auriez manqué. Voir QString::fromLatin1() pour plus d'informations. Désactiver la conversion peut rendre la programmation plus lourde.

Si votre langue source utilise des caractères hors du jeu Latin1, vous pourriez trouver QObject::trUtf8() plus pratique que QObject::tr(), étant donné que tr() dépend de QTextCodec::codecForTr(), ce qui le rend plus fragile que QObject::trUtf8().

Les valeurs de raccourcis clavier comme Ctrl+Q or Alt+F doivent elles aussi être traduites. Si vous écrivez directement Qt::CTRL + Qt::Key_Q pour « Quitter » dans votre application, les traducteurs ne pourront pas changer ces valeurs. L'idiome correct est :

     exitAct = new QAction(tr("Q&uitter"), this);
     exitAct->setShortcuts(QKeySequence::Quit);

Les fonctions QString::arg() fournissent une méthode simple de substitution des arguments :

 void FileCopier::showProgress(int done, int total,
                               const QString &currentFile)
 {
     label.setText(tr("%1 sur %2 fichiers copiés.\nCopie de : %3")
                   .arg(done)
                   .arg(total)
                   .arg(currentFile));
 }

Dans certaines langues, l'ordre des arguments doit être changé et cela peut être fait facilement en changeant l'ordre des arguments %. Par exemple :

 QString s1 = "%1 of %2 files copied. Copying: %3";
 QString s2 = "Kopierer nu %3. Av totalt %2 filer er %1 kopiert.";
 
 qDebug() << s1.arg(5).arg(10).arg("somefile.txt");
 qDebug() << s2.arg(5).arg(10).arg("somefile.txt");

Ceci produit la sortie correcte en anglais et en norvégien :

 5 of 10 files copied. Copying: somefile.txt
 Kopierer nu somefile.txt. Av totalt 10 filer er 5 kopiert.

Produire les traductions

Une fois que vous avez placé les

tr()

dans toute l'application, vous pouvez commencer à produire les traductions des textes visibles par l'utilisateur.

Le manuel de Qt Linguist contient des informations supplémentaires sur les outils de traduction de Qt, Qt Linguist, lupdate et lrelease.

La traduction d'une application Qt se déroule en trois étapes :

  1. exécuter lupdate pour extraire les textes à traduire du code source C++ de l'application Qt, ce qui produit un fichier message pour les traducteurs (un fichier TS). Cet utilitaire reconnait la construction tr() et les macros QT_TR*_NOOP() décrites ci-dessus et produit des fichiers TS (en principe, un par langue) ;
  2. effectuer les traductions des textes du fichier TS en utilisant Qt Linguist. Étant donné que les fichiers TS sont au format XML, vous pouvez aussi les éditer à la main ;
  3. exécuter lrelease pour créer un fichier message allégé (un fichier QM) à partir du fichier TS. On peut voir les fichiers TS comme des « fichiers source » et les fichiers QM comme des « fichiers objet ». Le traducteur édite les fichiers TS mais les utilisateurs de votre application ont seulement besoin des fichiers QM. Ces deux types de fichier sont indépendants de la plateforme et des paramètres régionaux.

Normalement, vous répéterez ces étapes à chaque version de votre application. L'utilitaire lupdate essaie de réutiliser au maximum les traductions des versions précédentes.

Avant de lancer lupdate, vous devez préparer un fichier projet. Voici un exemple de fichier projet (fichier .pro) :

 HEADERS         = funnydialog.h \
                   wackywidget.h
 SOURCES         = funnydialog.cpp \
                   main.cpp \
                   wackywidget.cpp
 FORMS           = fancybox.ui
 TRANSLATIONS    = superapp_dk.ts \
                   superapp_fi.ts \
                   superapp_no.ts \
                   superapp_se.ts

Quand vous lancez lupdate ou lrelease, vous devez donner le nom du fichier projet en argument de ligne de commande.

Dans cet exemple, quatre langues exotiques sont gérées : le danois, le finnois, le norvégien et le suédois. Si vous utilisez qmake, vous n'aurez en général pas besoin d'un fichier projet supplémentaire pour lupdate ; votre fichier de projet qmake fonctionnera correctement si vous ajoutez l'entrée TRANSLATIONS.

Dans votre application, vous devez charger les fichiers de traduction appropriés à la langue de l'utilisateur avec QTranslator::load() et les installer avec QCoreApplication::installTranslator().

linguist, lupdate and lrelease sont installés dans le sous-répertoire bin du répertoire de base dans lequel Qt est installé. Cliquez sur « Help|Manual » dans Qt Linguist pour accéder au manuel utilisateur ; il contient un tutoriel destinés aux débutants.

Qt lui-même contient quatre cents chaînes devant aussi être traduites dans les langues que vous ciblez. Vous trouverez des fichiers de traduction pour le français, l'allemand et le chinois simplifié dans $QTDIR/translations, ainsi qu'un modèle pour la traduction dans d'autres langues. Ce répertoire contient aussi des traductions non supportées qui peuvent être utiles.

Habituellement, la fonction main() de votre application ressemblera à ceci :

 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);
 
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(),
             QLibraryInfo::location(QLibraryInfo::TranslationsPath));
     app.installTranslator(&qtTranslator);
 
     QTranslator myappTranslator;
     myappTranslator.load("myapp_" + QLocale::system().name());
     app.installTranslator(&myappTranslator);
 
     ...
     return app.exec();
 }

Notez l'usage de QLibraryInfo::location() pour trouver l'emplacement des traductions de Qt. Les programmeurs doivent demander le chemin des traductions à l'exécution en passant QLibraryInfo::TranslationsPath à cette fonction au lieu d'utiliser la variable d'environnement QTDIR.

Gestion des encodages

La classe QTextCodec et les fonctionnalités de QTextStream facilitent la gestion de nombreux encodages des données de vos utilisateurs en entrée et en sortie. Lorsqu'une application est lancée, les paramètres régionaux de la machine vont déterminer l'encodage sur 8 bits utilisé pour des données 8 bits, comme pour la sélection de polices, l'affichage de texte, les entrées/sorties de texte 8 bits et les saisies de caractères.

L'application peut dans certains cas demander des encodages différents de l'encodage 8 bits local par défaut. Par exemple, une application en Cyrillic KOI8-R (l'encodage par défaut en Russie) peut avoir besoin de produire du cyrillique en encodage ISO 8859-5. Le code pour faire cela serait :

 QString string = ...; // du texte Unicode
 
 QTextCodec *codec = QTextCodec::codecForName("ISO 8859-5");
 QByteArray encodedString = codec->fromUnicode(string);

Pour la conversion de l'Unicode en encodage 8 bits local, il existe un raccourci : la fonction QString::toLocal8Bit() renvoie des données 8 bits localisées. Un autre raccourci utile est QString::toUtf8(), qui renvoie du texte en encodage 8 bits UTF-8 : ceci préserve parfaitement l'information Unicode tout en ayant l'apparence de l'ASCII simple si le texte est entièrement ASCII.

Pour la conversion en sens inverse, il existe les fonctions QString::fromUtf8() et QString::fromLocal8Bit(), ou le code général, illustré par cette conversion de ISO 8859-5 Cyrillic vers Unicode :

 QByteArray encodedString = ...; // du texte en encodage ISO 8859-5
 
 QTextCodec *codec = QTextCodec::codecForName("ISO 8859-5");
 QString string = codec->toUnicode(encodedString);

Idéalement, il faudrait utiliser les entrées-sorties Unicode, car elles maximisent la portabilité des documents entre les utilisateurs du monde entier mais, en réalité, il est utile de gérer tous les encodages dont vos utilisateurs auront besoin pour traiter les documents existants. En général, Unicode (UTF-16 ou UTF-8) est adapté pour les informations transférées entre destinataires arbitraires, alors que, à l'intérieur d'une langue ou d'un groupe national, un standard local est souvent plus approprié. L'encodage le plus important à gérer est celui renvoyé par QTextCodec::codecForLocale(), car c'est celui dont l'utilisateur a le plus de chances d'avoir besoin pour communiquer avec d'autres personnes et d'autres applications (c'est l'encodage utilisé par local8Bit()).

Qt gère nativement les encodages les plus utilisés. Pour une lise complète des encodages supportés, voir la documentation QTextCodec.

Dans certains cas et pour les encodages moins courants, il peut être nécessaire d'écrire votre propre classe dérivée de QTextCodec. Suivant l'urgence, il peut être utile de contacter le support technique de Qt ou de poster une question sur la liste de diffusion qt-interest pour savoir si quelqu'un d'autre travaille déjà sur cet encodage particulier.

Faire la localisation

La « localisation » est le processus d'adaptation aux conventions locales, par exemple la présentation des dates et des heures en utilisant le format préféré. Ceci peut se faire en utilisant des chaînes tr().

 void Clock::setTime(const QTime &time)
 {
     if (tr("AMPM") == "AMPM") {
         // horloge sur 12 heures
     } else {
         // horloge sur 24 heures
     }
 }

Dans l'exemple, pour les USA, nous laisserions telle quelle la traduction de « AMPM » et donc utiliserions la branche avec l'horloge sur 12 heures ; mais, en Europe, nous traduirions autrement et donc le code utiliserait la branche avec l'horloge sur 24 heures.

Pour la localisation des nombres, voir la classe QLocale.

La localisation des images n'est pas recommandée. Choisissez des images claires appropriées pour toutes les régions du monde plutôt que de vous appuyer sur des métaphores locales. Les images des flèches gauches et droites sont des exceptions pouvant nécessiter une inversion pour la langue arabe et l'hébreu.

Traductions dynamiques

Certaines applications, comme Qt Linguist, doivent être capables de gérer des changements des paramètres de langue de l'utilisateur en cours d'exécution. Pour que les widgets détectent les changements des QTranslators installés, réimplémentez la fonction changeEvent() pour vérifier si l'événement est de type LanguageChange et mettez à jour le texte affiché par les widgets en utilisant la fonction tr() de la façon habituelle. Par exemple :

 void MyWidget::changeEvent(QEvent *event)
 {
     if (e->type() == QEvent::LanguageChange) {
         titleLabel->setText(tr("Titre du document"));
         ...
         okPushButton->setText(tr("&OK"));
     } else
         QWidget::changeEvent(event);
 }

Tous les autres changements doivent être traités par l'appel de l'implémentation par défaut de la fonction.

La liste des traducteurs installés peut changer en réaction à un événement LocaleChange et l'application peut aussi fournir une interface permettant à l'utilisateur de changer la langue courante.

Le gestionnaire d'événements par défaut pour les classes dérivées de QWidget répond à l'événement QEvent::LanguageChange et appellera cette fonction si nécessaire.

Les événements LanguageChange sont postés lorsqu'une nouvelle traduction est installée en utilisant la fonction QCoreApplication::installTranslator(). De plus, d'autres composants de l'application peuvent forcer les widgets à se mettre à jour en leur postant des événements LanguageChange.

Traduction des classes non Qt

Il est parfois nécessaire de gérer l'internationalisation pour des chaînes utilisées dans des classes ne dérivant pas de QObject et n'utilisant pas la macro Q_OBJECT pour activer les fonctions de traduction. Comme Qt traduit les chaînes à l'exécution en fonction de la classe à laquelle elle sont associées et que lupdate cherche les chaînes à traduire dans le code source, les classes non Qt doivent utiliser des mécanismes fournissant également cette information.

Une manière d'activer la traduction pour une classe non Qt est d'utiliser la macro Q_DECLARE_TR_FUNCTIONS() ; par exemple :

 class MyClass
 {
     Q_DECLARE_TR_FUNCTIONS(MyClass)
 
 public:
     MyClass();
     ...
 };

Ce code fournit à la classe les fonctions tr() qui peuvent être utilisées pour traduire les chaînes associées à la classe, et permet à lupdate de trouver les chaînes à traduire dans le code source.

Une alternative est d'utiliser la fonction QCoreApplication::translate() qui peut être appelée avec un contexte spécifique, qui sera reconnu par lupdate et Qt Linguist.

Support système

Certains systèmes d'exploitation et systèmes de fenêtrage sur lesquels Qt fonctionne ont un support limité d'Unicode. Le niveau de support disponible dans le système sous-jacent influence le support que Qt peut fournir sur ces plateformes, bien qu'en général les applications Qt ne devraient pas avoir trop à se soucier des limitations spécifiques aux plateformes.

Unix/X11

  • Polices et méthodes de saisie dépendant des paramètres régionaux. Qt les remplace par des entrées et sorties Unicode.
  • Les conventions de système de fichiers comme UTF sont en développement dans certaines variantes d'Unix. Toutes les fonctions sur les fichiers de Qt autorisent Unicode mais convertissent les noms de fichier en encodage 8 bits local, ce qui est la convention Unix (voir QFile::setEncodingFunction() pour explorer les encodages alternatifs).
  • Les entrées-sorties fichier se font par défaut en encodage 8 bits local, avec des options Unicode dans QTextStream.
  • De nombreuses distributions Unix ne contiennent qu'un support partiel de certaines options régionales. Par exemple, avoir un répertoire /usr/share/locale/ja_JP.EUC ne signifie pas forcément que vous pouvez afficher du texte japonais ; vous avez aussi besoin de polices encodées en JIS (ou de polices Unicode) et le répertoire /usr/share/locale/ja_JP.EUC doit être complet. Il est conseillé d'utiliser des localisations complètes produites par le fournisseur du système.

Windows

  • Qt fournit une gestion complète d'Unicode, incluant les méthodes de saisie, les polices, le presse-papier, le glisser-déposer et les noms de fichier.
  • Les entrées-sorties fichier se font par défaut en Latin1, avec des options Unicode dans QTextStream. Notez que certains programmes Windows ne comprennent pas les fichier texte Unicode big-endian, alors qu'il s'agit de l'ordre prescrit par le standard Unicode en l'absence de protocole de plus haut niveau.

Mac OS X

Pour les détails des spécificités de la traduction sur Mac OS X, voir ce document.

[ Suivant : Écrire du code source pour la traduction ]

Remerciements

Merci à <!idiallo!> pour la traduction et ainsi qu'à <!dourouc!> et <!eusebe!> pour la relecture !

Warning: include(): https:// wrapper is disabled in the server configuration by allow_url_include=0 in /home/developpez/www/developpez-com/upload/qt/doc/bs.php on line 4 Warning: include(https://qt.developpez.com/index/rightColumn): failed to open stream: no suitable wrapper could be found in /home/developpez/www/developpez-com/upload/qt/doc/bs.php on line 4 Warning: include(): Failed opening 'https://qt.developpez.com/index/rightColumn' for inclusion (include_path='.:/usr/php53/lib/php') in /home/developpez/www/developpez-com/upload/qt/doc/bs.php on line 4
Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.7
Copyright © 2019 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur, une redirection cassée ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
Responsable bénévole de la rubrique Qt : Thibaut Cuvelier -

Partenaire : Hébergement Web