version 0.2 v0.2
authorAntoine Jacquet <royale@zerezo.com>
Wed, 15 May 2002 22:00:00 +0000 (00:00 +0200)
committerAntoine Jacquet <royale@zerezo.com>
Wed, 15 May 2002 22:00:00 +0000 (00:00 +0200)
* languages support (currently english and french)
* themes support
* most used words
* now handles personal messages and "/me"
* parameters on command line
* some code change
* faster
* no more image when 0 lines
* maximum height for graph is now calculated

README
irssistats.c

diff --git a/README b/README
index d2ee540..da6ed11 100644 (file)
--- a/README
+++ b/README
@@ -1,16 +1,13 @@
-irssistats 0.1
+irssistats 0.2
 site: http://royale.zerezo.com/programmation/irssistats/
 mail: royale@zerezo.com
 
-config:
-edit irssistats.c
-
 install:
 make
 cp pix/*.png /path/to/webdir/
 
 usage:
-cat /path/to/file.log | ./irssistats > /path/to/webdir/index.html
+cat /path/to/file.log | ./irssistats channel maintainer language theme > /path/to/webdir/index.html
 
 links:
 http://torus.lnet.lut.fi/ircstats/
index e9a26c3..62d1194 100644 (file)
-/* Usage: cat /path/to/file.log | ./irssistats > /path/to/file.html */
+/* Usage: cat /path/to/file.log | ./irssistats channel maintainer language theme > /path/to/file.html */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
 
 /* Config */
-#define CHANNEL "#channel"
-#define MAINTAINER "maintainer"
+#define MAXUSERS 2000
+#define MAXNICKLENGTH 50
+#define MAXLINELENGTH 2000
+#define MAXQUOTELENGTH 100
+#define NBUSERS 50
+#define NBURLS 5
+#define NBTOPICS 5
+#define NBWORDS 20
+#define MINWORDLENGTH 5
 
-/* Language */
-#define HEADER "Statistiques de %s par %s"
-#define LEGEND "L&eacute;gende"
-#define LASTDAYS "Statistiques des derniers jours"
-#define TOPHOURS "Statistiques horaires"
-#define TOPUSERS "Personnes les plus actives"
-#define OTHERS "Il reste %d personnes non class&eacute;es..."
-#define NBLINES "lignes"
-#define NICK "nick"
-#define AVGLETTERS "lettres/lignes"
-#define HOURS "heures"
-#define QUOTE "message al&eacute;atoire"
-#define RANDTOPICS "Quelques topics"
-#define CHANGEDBY "chang&eacute; par"
-#define NEWTOPIC "nouveau topic"
-#define RANDURLS "Quelques URLs"
-#define POSTEDBY "post&eacute;e par"
-#define POSTEDURL "URL"
-#define BIGNUMBERS "Quelques grands nombres..."
-#define TIME "%d lignes trait&eacute;es en %d secondes"
-#define FOOTER "Statistiques g&eacute;n&eacute;r&eacute;es par"
+/* irssistats */
+#define VERSION "0.2"
+#define URL "http://royale.zerezo.com/programmation/irssistats/"
 
-/* Colors */
-#define BGCOLOR "#FFFFFF"
-#define TEXT "#000000"
-#define LINK "#0000EE"
-#define VLINK "#551A8B"
-#define ALINK "#FF0000"
-#define TITLE1 "#000000"
-#define TITLE2 "#000000"
-#define BGTABLE "#EEEEEE"
-#define BGTITLE "#FFEEEE"
+/* Languages */
+#define NBLANGUAGES 2
+#define NBKEYS 22
+char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and abbreviation */
+{
+  { /* English language */
+    { "English",     "en" },
+    { "HEADER",      "Statistics for #%s by %s" },
+    { "LEGEND",      "Legend" },
+    { "LASTDAYS",    "Lastdays statistics" },
+    { "TOPHOURS",    "Hourly statistics" },
+    { "TOPUSERS",    "Most active people" },
+    { "OTHERS",      "There are %d left not ranked..." },
+    { "NBLINES",     "lines" },
+    { "NICK",        "nick" },
+    { "AVGLETTERS",  "letters/lines" },
+    { "HOURS",       "hours" },
+    { "QUOTE",       "random message" },
+    { "RANDTOPICS",  "Some topics" },
+    { "CHANGEDBY",   "changed by" },
+    { "NEWTOPIC",    "new topic" },
+    { "RANDURLS",    "Some URLs" },
+    { "POSTEDBY",    "posted by" },
+    { "POSTEDURL",   "URL" },
+    { "TOPWORDS",    "Most used words" },
+    { "WORD",        "word" },
+    { "OCCURRENCES", "occurrences" },
+    { "TIME",        "%d lines parsed in %d seconds" },
+    { "FOOTER",      "Statistics generated by" }
+  },
+  { /* French language */
+    { "Français",    "fr" },
+    { "HEADER",      "Statistiques de #%s par %s" },
+    { "LEGEND",      "L&eacute;gende" },
+    { "LASTDAYS",    "Statistiques des derniers jours" },
+    { "TOPHOURS",    "Statistiques horaires" },
+    { "TOPUSERS",    "Personnes les plus actives" },
+    { "OTHERS",      "Il reste %d personnes non class&eacute;es..." },
+    { "NBLINES",     "lignes" },
+    { "NICK",        "nick" },
+    { "AVGLETTERS",  "lettres/lignes" },
+    { "HOURS",       "heures" },
+    { "QUOTE",       "message al&eacute;atoire" },
+    { "RANDTOPICS",  "Quelques topics" },
+    { "CHANGEDBY",   "chang&eacute; par" },
+    { "NEWTOPIC",    "nouveau topic" },
+    { "RANDURLS",    "Quelques URLs" },
+    { "POSTEDBY",    "post&eacute;e par" },
+    { "POSTEDURL",   "URL" },
+    { "TOPWORDS",    "Mots les plus utilis&eacute;s" },
+    { "WORD",        "mot" },
+    { "OCCURRENCES", "occurrences" },
+    { "TIME",        "%d lignes trait&eacute;es en %d secondes" },
+    { "FOOTER",      "Statistiques g&eacute;n&eacute;r&eacute;es par" }
+  }
+};
 
-/* Dark Theme... */
-/*
-#define BGCOLOR "#000000"
-#define TEXT "#FFFFFF"
-#define LINK "#AAAAFF"
-#define VLINK "#CCCCDD"
-#define ALINK "#FFAAAA"
-#define TITLE1 "#AAAAFF"
-#define TITLE2 "#FFAAAA"
-#define BGTABLE "#225522"
-#define BGTITLE "#552222"
-*/
+int language;
 
+char *L(char *key)
+{
+  int i;
+  for (i=1;i<=NBKEYS;i++) if (strcmp(key,keys[language][i][0])==0) return(keys[language][i][1]);
+  fprintf(stderr,"unknown language key: %s\n",key);
+  return("");
+}
 
-/* irssistats */
-#define VERSION "0.1"
-#define URL "http://royale.zerezo.com/programmation/irssistats/"
+/* Themes */
+#define NBTHEMES 3
+#define NBCOLORS 9
+char *colors[NBTHEMES][NBCOLORS+1][2]= /* first key used for theme name/description and abbreviation */
+{
+  { /* Default theme */
+    { "White background", "default" },
+    { "BGCOLOR", "#FFFFFF" },
+    { "TEXT",    "#000000" },
+    { "LINK",    "#0000EE" },
+    { "VLINK",   "#551A8B" },
+    { "ALINK",   "#FF0000" },
+    { "TITLE1",  "#000000" },
+    { "TITLE2",  "#000000" },
+    { "BGTABLE", "#EEEEEE" },
+    { "BGTITLE", "#FFEEEE" }
+  },
+  { /* Dark theme */
+    { "Black background", "dark" },
+    { "BGCOLOR", "#000000" },
+    { "TEXT",    "#FFFFFF" },
+    { "LINK",    "#AAAAFF" },
+    { "VLINK",   "#CCCCDD" },
+    { "ALINK",   "#FFAAAA" },
+    { "TITLE1",  "#AAAAFF" },
+    { "TITLE2",  "#FFAAAA" },
+    { "BGTABLE", "#225522" },
+    { "BGTITLE", "#552222" }
+  },
+  { /* zeRezo theme */
+    { "Green theme...", "zerezo" },
+    { "BGCOLOR", "#000000" },
+    { "TEXT",    "#FFFFFF" },
+    { "LINK",    "#14F024" },
+    { "VLINK",   "#0CBA18" },
+    { "ALINK",   "#FFFFFF" },
+    { "TITLE1",  "#0CBA18" },
+    { "TITLE2",  "#84DB8C" },
+    { "BGTABLE", "#085D10" },
+    { "BGTITLE", "#0B810B" }
+  }
+};
 
+int theme;
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <time.h>
-#include <string.h>
+char *T(char *color)
+{
+  int i;
+  for (i=1;i<=NBCOLORS;i++) if (strcmp(color,colors[theme][i][0])==0) return(colors[theme][i][1]);
+  fprintf(stderr,"unknown theme color: %s\n",color);
+  return("");
+}
 
-#define MAXUSERS 2000
-#define MAXNICKLENGTH 50
-#define MAXLINELENGTH 2000
+/* Variables */
+
+char *channel;
+char *maintainer;
 
 struct 
 {
@@ -99,6 +179,69 @@ int days=0;
 int hours[24];
 int lines=0;
 
+struct letter
+{
+  int nb;
+  struct letter *next[26];
+} words;
+
+struct
+{
+  int nb;
+  char word[MAXLINELENGTH];
+} topwords[NBWORDS];
+
+#define isletter(c) (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z')))
+#define lowercase(c) (((c>='A')&&(c<='Z'))?c-'A'+'a':c)
+void findwords(char *message)
+{
+  int i,c;
+  struct letter *pos,*tmp;
+  for (;;)
+  {
+    while (!isletter(*message)) if (*message=='\0') return; else message++;
+    pos=&words;
+    while (isletter(*message))
+    {
+      c=lowercase(*message)-'a';
+      if ((*pos).next[(int)c]==NULL)
+      {
+        tmp=malloc(sizeof(struct letter));
+        (*tmp).nb=0;
+        for (i=0;i<26;i++) (*tmp).next[i]=NULL;
+        (*pos).next[(int)c]=tmp;
+      }
+      pos=(*pos).next[(int)c];
+      message++; 
+    }
+    (*pos).nb++;
+  }
+  return;
+}
+
+char tempword[MAXLINELENGTH];
+void bestwords(struct letter pos,int cur)
+{
+  int i,j;
+  if ((cur>=MINWORDLENGTH)&&(pos.nb>topwords[NBWORDS-1].nb))
+  {
+    for (i=0;pos.nb<topwords[i].nb;i++);
+    for (j=NBWORDS-1;j>i;j--)
+    {
+      topwords[j].nb=topwords[j-1].nb;
+      strcpy(topwords[j].word,topwords[j-1].word);
+    }
+    topwords[i].nb=pos.nb;
+    strcpy(topwords[i].word,tempword);
+  }
+  for (i=0;i<26;i++) if (pos.next[i]!=NULL)
+  {
+    tempword[cur]='a'+i;
+    bestwords(*(pos.next[i]),cur+1);
+  }
+  tempword[cur]='\0';
+}
+
 void unhtml(char *string) /* replace < and > by { and } */
 {
   while (*string!='\0')
@@ -110,6 +253,35 @@ void unhtml(char *string) /* replace < and > by { and } */
   return;
 }
 
+int dichotomic(char *nick)
+{
+  int i,j,start=0,end=nbusers-1,middle;
+  while (start<=end)
+  {
+    middle=(start+end)/2;
+    if (strcmp(nick,users[middle].nick)>0) start=middle+1; else end=middle-1;
+  }
+  if (strcmp(nick,users[start].nick)!=0)
+  {
+    nbusers++;
+    if (nbusers>=MAXUSERS) { fprintf(stderr,"too many users\n"); exit(1); }
+    for (i=nbusers-1;i>start;i--)
+    {
+      strcpy(users[i].nick,users[i-1].nick);
+      users[i].lines=users[i-1].lines;
+      users[i].letters=users[i-1].letters;
+      for (j=0;j<4;j++) users[i].hours[j]=users[i-1].hours[j];
+      strcpy(users[i].quote,users[i-1].quote);
+    }
+    strcpy(users[start].nick,nick);
+    users[start].lines=0;
+    users[start].letters=0;
+    for (j=0;j<4;j++) users[start].hours[j]=0;
+    users[start].quote[0]='\0';
+  }
+  return(start);
+}
+
 int main(int argc,char *argv[])
 {
   int i,j;
@@ -121,10 +293,35 @@ int main(int argc,char *argv[])
   char *nick,*message;
   char line[MAXLINELENGTH];
   
+  /*** INIT ***/
+  if (argc!=5)
+  {
+    fprintf(stderr,"Usage: cat /path/to/file.log | ./irssistats channel maintainer language theme > /path/to/file.html\n\n");
+    fprintf(stderr,"Supported languages :\n");
+    for (i=0;i<NBLANGUAGES;i++) fprintf(stderr,"%s = %s\n",keys[i][0][1],keys[i][0][0]);
+    fprintf(stderr,"\nSupported themes :\n");
+    for (i=0;i<NBTHEMES;i++) fprintf(stderr,"%s = %s\n",colors[i][0][1],colors[i][0][0]);
+    return(1);
+  }
+  channel=argv[1];
+  maintainer=argv[2];
+  for (i=0;i<NBLANGUAGES;i++) if (strcmp(argv[3],keys[i][0][1])==0) { language=i; break; }
+  if (i==NBLANGUAGES)
+  {
+    fprintf(stderr,"Invalid language : %s\n",argv[3]);
+    return(1);
+  }
+  for (i=0;i<NBTHEMES;i++) if (strcmp(argv[4],colors[i][0][1])==0) { theme=i; break; }
+  if (i==NBTHEMES)
+  {
+    fprintf(stderr,"Invalid theme : %s\n",argv[4]);
+    return(1);
+  }
+
   /*** LOG ***/
   
   srand(debut=time(NULL));
-  fprintf(stderr,"working");
+  fprintf(stderr,"working:");
   while (!feof(stdin))
   {
     c=getchar();
@@ -171,139 +368,161 @@ int main(int argc,char *argv[])
           }
         }
       }
-      else  /* 00:00 <?Nick> the message */
+      else if ((line[6]=='<') || (line[7]=='*'))
       {
         line[2]='\0';
         hour=atoi(line);
-        if ((line[6]=='<') && (line[7]!='>'))
+        if (line[7]=='*') /* 00:00  * Nick the message */
+        {
+          for (i=9;line[i]!=' ';i++);
+          nick=&line[9];
+          message=&line[i+1];
+        }
+        else if (line[7]=='>') /* 00:00 <>>>?Nick<<<> the personal message */
         {
-          for (i=7;line[i]!='>';i++);
-          line[i]='\0';
+          for (i=11;line[i]!='<';i++);
+          nick=&line[11];
+          message=&line[i+5];
+        }
+        else /* 00:00 <?Nick> the message */
+        {
+          for (i=8;line[i]!='>';i++);
           nick=&line[8];
-          unhtml(nick);
-          line[pos-1]='\0';
           message=&line[i+2];
-          unhtml(message);
-          for (i=0;i<nbusers;i++) if (strcmp(nick,users[i].nick)==0) break;
-          if (i==nbusers)
-          {
-            strcpy(users[nbusers].nick,nick);
-            nbusers++;
-            if (nbusers>=MAXUSERS) { fprintf(stderr,"too many users\n"); exit(1); }
-          }
-          users[i].lines++;
-          users[i].letters+=strlen(message);
-          users[i].hours[hour/6]++;
-          lastdays[0].lines++;
-          lastdays[0].hours[hour/6]++;
-          lines++;
-          hours[hour]++;
-          if (rand()%users[i].lines==0) strncpy(users[i].quote,message,100);
-          if (strncmp("http://",message,7)==0)
+        }
+        line[i]='\0';
+        line[pos-1]='\0';
+        unhtml(nick);
+        unhtml(message);        
+        i=dichotomic(nick);
+        users[i].lines++;
+        users[i].letters+=strlen(message);
+        users[i].hours[hour/6]++;
+        lastdays[0].lines++;
+        lastdays[0].hours[hour/6]++;
+        lines++;
+        hours[hour]++;
+        if (rand()%users[i].lines==0) strncpy(users[i].quote,message,MAXQUOTELENGTH);
+        if (strncmp("http://",message,7)==0)
+        {
+          for (i=0;(message[i]!=' ') && (i<strlen(message));i++);
+          message[i]='\0';
+          nburls++;
+          if ((nburls<=5) || (rand()%nburls==0))
           {
-            for (i=0;(message[i]!=' ') && (i<strlen(message));i++);
-            message[i]='\0';
-            nburls++;
-            if ((nburls<=5) || (rand()%nburls==0))
+            for (i=4;i>0;i--)
             {
-              for (i=4;i>0;i--)
-              {
-                strcpy(urls[i].nick,urls[i-1].nick);
-                strcpy(urls[i].url,urls[i-1].url);
-              }
-              strcpy(urls[0].nick,nick);
-              strcpy(urls[0].url,message);
+              strcpy(urls[i].nick,urls[i-1].nick);
+              strcpy(urls[i].url,urls[i-1].url);
             }
+            strcpy(urls[0].nick,nick);
+            strcpy(urls[0].url,message);
           }
         }
+        findwords(message);
       }
       pos=0;
     }
   }
   fprintf(stderr,"done\n");
 
+  bestwords(words,0);
+
   /*** HTML ***/
 
   /* header */
   printf("<!-- Generated by irssistats %s : %s -->\n\n",VERSION,URL);
   printf("<html>\n\n<head>\n<base target=\"_blank\">\n<title>");
-  printf(HEADER,CHANNEL,MAINTAINER);
+  printf(L("HEADER"),channel,maintainer);
   printf("</title>\n</head>\n\n");
-  printf("<body bgcolor=\"%s\" text=\"%s\" link=\"%s\" vlink=\"%s\" alink=\"%s\">\n\n<center>\n\n<font color=\"%s\"><h1>",BGCOLOR,TEXT,LINK,VLINK,ALINK,TITLE1);
-  printf(HEADER,CHANNEL,MAINTAINER);
+  printf("<body bgcolor=\"%s\" text=\"%s\" link=\"%s\" vlink=\"%s\" alink=\"%s\">\n\n<center>\n\n<font color=\"%s\"><h1>",T("BGCOLOR"),T("TEXT"),T("LINK"),T("VLINK"),T("ALINK"),T("TITLE1"));
+  printf(L("HEADER"),channel,maintainer);
   printf("</h1></font>\n%s<br>\n<br><br>\n\n",ctime(&debut));
 
   /* legend */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table bgcolor=%s>\n<tr>\n",TITLE2,LEGEND,BGTABLE);
-  for (i=0;i<4;i++) printf("<td><img src=\"h%d.png\" width=\"40\" height=\"15\"></td><td> : %s %d-%d&nbsp;</td><td width=\"10\"></td>\n",i+1,HOURS,i*6,i*6+5);
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table bgcolor=%s>\n<tr>\n",T("TITLE2"),L("LEGEND"),T("BGTABLE"));
+  for (i=0;i<4;i++) printf("<td><img src=\"h%d.png\" width=\"40\" height=\"15\"></td><td> : %s %d-%d&nbsp;</td><td width=\"10\"></td>\n",i+1,L("HOURS"),i*6,i*6+5);
   printf("</tr>\n</table>\n<br><br>\n\n");
   
   /* last days */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",TITLE2,LASTDAYS);
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",T("TITLE2"),L("LASTDAYS"));
   max=-1;
   for (i=30;i>=0;i--) if (lastdays[i].lines>max) max=lastdays[i].lines;
   for (i=30;i>=0;i--)
   {
-    printf("<td align=\"center\" valign=\"bottom\"><font size=\"-2\">%d</font><br>",lastdays[i].lines);
-    if (max!=0) for (j=0;j<4;j++) printf("<img src=\"v%d.png\" width=\"15\" height=\"%d\"><br>",j+1,150*lastdays[i].hours[j]/max);
+    printf("<td width=\"15\" align=\"center\" valign=\"bottom\"><font size=\"-2\">%d</font><br>",lastdays[i].lines);
+    for (j=0;j<4;j++) if (lastdays[i].hours[j]!=0) printf("<img src=\"v%d.png\" width=\"15\" height=\"%d\"><br>",j+1,150*lastdays[i].hours[j]/max);
     printf("</td>\n");
   }
   printf("</tr>\n<tr>\n");
   for (i=30;i>=0;i--)
-    printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",BGTABLE,i);
+    printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",T("BGTABLE"),i);
   printf("</tr>\n</table>\n<br><br>\n\n");
   
   /* top hours */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",TITLE2,TOPHOURS);
-  if (lines!=0) for (i=0;i<24;i++)
-    printf("<td align=\"center\" valign=\"bottom\"><font size=\"-2\">%.1f%%</font><br><img src=\"v%d.png\" width=\"15\" height=\"%d\"></td>\n",(float)100*hours[i]/lines,i/6+1,1500*hours[i]/lines);
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",T("TITLE2"),L("TOPHOURS"));
+  max=-1;
+  for (i=0;i<24;i++) if (hours[i]>max) max=hours[i];
+  for (i=0;i<24;i++)
+  {
+    printf("<td width=\"15\" align=\"center\" valign=\"bottom\"><font size=\"-2\">%.1f%%</font><br>",lines!=0?(float)100*hours[i]/lines:0);
+    if (hours[i]!=0) printf("<img src=\"v%d.png\" width=\"15\" height=\"%d\"><br>",i/6+1,150*hours[i]/max);
+    printf("</td>\n");
+  }
   printf("</tr>\n<tr>\n");
   for (i=0;i<24;i++)
-    printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",BGTABLE,i);
+    printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",T("BGTABLE"),i);
   printf("</tr>\n</table>\n<br><br>\n\n");
   
   /* top users */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n",TITLE2,TOPUSERS);
-  printf("<table>\n<tr><td></td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\" colspan=\"2\">%s</td><td bgcolor=\"%s\">%s</td>\n",BGTITLE,NICK,BGTITLE,NBLINES,BGTITLE,HOURS,BGTITLE,AVGLETTERS,BGTITLE,QUOTE);
-  for (i=1;i<=50;i++)
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("TOPUSERS"));
+  printf("<table>\n<tr><td></td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\" colspan=\"2\">%s</td><td bgcolor=\"%s\">%s</td>\n",T("BGTITLE"),L("NICK"),T("BGTITLE"),L("NBLINES"),T("BGTITLE"),L("HOURS"),T("BGTITLE"),L("AVGLETTERS"),T("BGTITLE"),L("QUOTE"));
+  for (i=1;i<=NBUSERS;i++)
   {
     user=-1;
     max=-1;
     for (j=0;j<nbusers;j++) if (users[j].lines>max) max=users[user=j].lines;
     if (user!=-1)
     {
-      printf("<tr><td bgcolor=\"%s\" align=\"right\">%d</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%d</td><td bgcolor=\"%s\">",BGTABLE,i,BGTABLE,users[user].nick,BGTABLE,users[user].lines,BGTABLE);
-      for (j=0;j<4;j++)
-        printf("<img src=\"h%d.png\" width=\"%d\" height=\"15\">",j+1,100*users[user].hours[j]/users[user].lines);
-      printf("</td><td bgcolor=\"%s\">%d</td><td bgcolor=\"%s\"><img src=\"hm.png\" width=\"%d\" height=\"15\"></td><td bgcolor=\"%s\">\"%s\"</td></tr>\n",BGTABLE,users[user].letters/users[user].lines,BGTABLE,users[user].letters/users[user].lines,BGTABLE,users[user].quote);
+      printf("<tr><td bgcolor=\"%s\" align=\"right\">%d</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%d</td><td bgcolor=\"%s\">",T("BGTABLE"),i,T("BGTABLE"),users[user].nick,T("BGTABLE"),users[user].lines,T("BGTABLE"));
+      for (j=0;j<4;j++) if (users[user].hours[j]!=0) printf("<img src=\"h%d.png\" width=\"%d\" height=\"15\">",j+1,100*users[user].hours[j]/users[user].lines);
+      printf("</td><td bgcolor=\"%s\">%d</td><td bgcolor=\"%s\"><img src=\"hm.png\" width=\"%d\" height=\"15\"></td><td bgcolor=\"%s\">\"%s\"</td></tr>\n",T("BGTABLE"),users[user].letters/users[user].lines,T("BGTABLE"),users[user].letters/users[user].lines,T("BGTABLE"),users[user].quote);
       users[user].lines=-1;
     }    
   }
-  printf("</table><br>\n");
-  if (nbusers>50) printf(OTHERS,nbusers-50);
-  printf("<br>\n<br><br>\n\n");
+  printf("</table>\n");
+  if (nbusers>NBUSERS)
+  {
+    printf("<br>");
+    printf(L("OTHERS"),nbusers-50);
+    printf("<br>\n");
+  }
+  printf("<br><br>\n\n");
 
   /* random topics */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n",TITLE2,RANDTOPICS);
-  printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",BGTITLE,CHANGEDBY,BGTITLE,NEWTOPIC);
-  for (i=4;i>=0;i--)
-    printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"%s\"</td></tr>\n",BGTABLE,topics[i].nick,BGTABLE,topics[i].topic);
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("RANDTOPICS"));
+  printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",T("BGTITLE"),L("CHANGEDBY"),T("BGTITLE"),L("NEWTOPIC"));
+  for (i=nbtopics<NBTOPICS?nbtopics-1:NBTOPICS-1;i>=0;i--)
+    printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"%s\"</td></tr>\n",T("BGTABLE"),topics[i].nick,T("BGTABLE"),topics[i].topic);
   printf("</table>\n<br><br>\n\n");
   
   /* random urls */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n",TITLE2,RANDURLS);
-  printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",BGTITLE,POSTEDBY,BGTITLE,POSTEDURL);
-  for (i=4;i>=0;i--)
-    printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"<a href=\"%s\">%s</a>\"</td></tr>\n",BGTABLE,urls[i].nick,BGTABLE,urls[i].url,urls[i].url);
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("RANDURLS"));
+  printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",T("BGTITLE"),L("POSTEDBY"),T("BGTITLE"),L("POSTEDURL"));
+  for (i=nburls<NBURLS?nburls-1:NBURLS-1;i>=0;i--)
+    printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"<a href=\"%s\">%s</a>\"</td></tr>\n",T("BGTABLE"),urls[i].nick,T("BGTABLE"),urls[i].url,urls[i].url);
   printf("</table>\n<br><br>\n\n");
   
-  /* big numbers (todo...)
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n",TITLE2,BIGNUMBERS);
-  */
+  /* top words */
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("TOPWORDS"));
+  printf("<table>\n<tr><td></td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",T("BGTITLE"),L("WORD"),T("BGTITLE"),L("OCCURRENCES"));
+  for (i=0;i<NBWORDS;i++)
+    if (topwords[i].nb!=0) printf("<tr><td bgcolor=\"%s\" align=\"right\">%d</td><td bgcolor=\"%s\">\"%s\"</td><td bgcolor=\"%s\">%d</td></tr>\n",T("BGTABLE"),i+1,T("BGTABLE"),topwords[i].word,T("BGTABLE"),topwords[i].nb);
+  printf("</table>\n<br><br>\n\n");
   
   /* footer */
-  printf(TIME,totallines,(int)(time(NULL)-debut));
-  printf("<br>\n%s <a href=\"%s\">irssistats %s</a>",FOOTER,URL,VERSION);
+  printf(L("TIME"),totallines,(int)(time(NULL)-debut));
+  printf("<br>\n%s <a href=\"%s\">irssistats %s</a>",L("FOOTER"),URL,VERSION);
   printf("\n\n</center>\n\n</body>\n\n</html>\n");
   
   return(0);