Debian squeeze en Samsung N210

La semana pasada me compré una netbook Samsung N210 con la cual estoy muy contento. Le instalé Debian squeeze (testing) y logré configurarle todas las teclas especiales, incluyendo las usadas para regular el brillo de la pantalla. Me basé en varias guías como esta y esta otra, aunque no las seguí exactamente y es por eso el motivo del post.

Antes de comenzar la guía (y suponiendo que como yo, estás leyendo esto antes de comprar la máquina) te cuento mi experiencia con este aparatito. Algo que me hacía dudar en comprarme o no una netbook era el teclado que traían: demasiado chicos o sin espacio entre teclas. La N210 tiene casi la misma distancia entre la A y la Ñ que los teclados de escritorio y además tiene las teclas separadas como para que los dedos reconozcan mejor dónde están ubicados. Lo encuentro bastante cómodo y agradable de usar y salvo por un par de teclas que complican un poco (la < se encuentra sobre la derecha y el shift derecho es chico) me siento a gusto con él. Por otro lado la batería dura aprox. 7 hs (con el brillo casi a tope) contra las 11 que declara Samsung. Dudo que llegue a eso incluso desconectando la wifi, bluetooth y poniendo el brillo al mínimo, pero aun así destaca su autonomía. Otra característica que me gusta es que es silenciosa y calienta muy poco.

Una última cosa antes de comenzar: El microprocesador Atom N450 tiene tecnología Hyperthreading que paraleliza el procesador físico en dos procesadores virtuales. Es por eso que la salida de/proc/cpuinfomuestra dos cpus.

Ahora sí, vamos a la guía.

Read more »

Tags: , ,

Reemplazo de discos en RAID con mdadm

Hace unos días hice mi segundo reemplazo completo de discos de un array MD y quería compartir aquí los pasos que he utilizado (con éxito :D) en ambos casos.

Los dos escenarios fueron similares: dos discos iguales, con iguales particiones y RAID-1 que deben ser reemplazados por otros dos discos iguales pero de mayor tamaño, por lo que el procedimiento fue el mismo en ambos casos.

Para simplificar la guía decidí usar discos con sólo tres particiones: una para/, una para/vary otra paraswap(que no está en RAID). El esquema entonces queda definido de la siguiente manera:

sda (grub)               RAID's 1                sdb (grub)
+------------------+     +-----------------+     +------------------+
| /dev/sda1        |-----| /dev/md0 (/)    |-----| /dev/sdb1        |
+------------------+     +-----------------+     +------------------+
| /dev/sda2        |-----| /dev/md1 (/var) |-----| /dev/sdb2        |
+------------------+     +-----------------+     +------------------+
| /dev/sda3 (swap) |                             | /dev/sdb3 (swap) |
+------------------+                             +------------------+

Los discos que se usarán para reemplazo sonsdcysdd.

NOTA: En la guía sólo se muestra el reemplazo completo de sda por sdc ya que el procedimiento para reemplazar sdb por sdd es el mismo.

1. Particionado de disco

La manera sencilla de copiar la tabla de particiones de una unidad a otra es:

papua:~# sfdisk -d /dev/sda | sfdisk /dev/sdc

Esto copia completamente el esquema del disco origen (sda) al disco destino (sdc). Si el destino es mayor simplemente quedará espacio libre (sin asignar) al final del disco.

2. Reemplazo de particiones en arrays

TIP: Es útil tener visible, en otra terminal y durante todo el proceso, la salida de/proc/mdstatpara visualizar en todo momento el estado del array y poder ver el sincronizado de los discos:

papua:~# watch -t cat /proc/mdstat

Read more »

Tags:

Vim scripting con Python

Vim permite agregar funcionalidades mediante varios lenguajes externos, entre ellos Perl, Python, Ruby y Tcl. Para poder hacer uso de esta característica es necesario haber compilado Vim con el soporte para el lenguaje necesario (con el flag-python, para soporte python). En debian existe un paquete llamado vim-nox (no X) que contiene una versión compilada con soporte para los cuatro lenguajes mencionados:

sudo apt-get install vim-nox

Para comprobar que efectivamente se encuentre habilitado el soporte para Python ejecutar Vim y en modo comando escribir:

:echo has("python")

Un 1 indica que Vim interpreta Python ;)

Es muy recomendable leer la ayuda en línea:

:help python

También se puede acceder vía web desde http://vimdoc.sourceforge.net/htmldoc/if_pyth.html

Ejecución de código Python

La ejecución de una línea de código Python se realiza según la sintaxis:

:[range]py[thon] {stmt}

Ejemplo:

:python print "hello world"

Aquí el prompt mostrará el mensaje “hello world”.

En cambio para escribir código en varias líneas hay que usar la siguiente forma:

[range]:py[thon] < < {endmarker}
{script}
{endmarker}

Ejemplo:

:python < < EOF
def get_user():
  import os
  return os.getenv('USER')
EOF

De esta manera la función queda guardada en la memoria de la sesión actual. Luego su uso podría ser el siguiente:

:py user = get_user()

Tambien es posible cargar y/o ejecutar código desde un archivo externo. La sintaxis es:

:[range]pyf[ile] {file}

Ejemplo:

:pyfile myscript.py

Simyscript.pynecesitara parámetros la forma de pasárselos es la siguiente:

:py import sys
:py sys.argv = ['foo', 'bar']
:pyf myscript.py

El módulo vim

La comunicación entre Python y Vim se realiza a través del módulo vim, el cual hay que importar antes de usar:

:python import vim

Como indica la ayuda de Vim, el módulo implementa dos métodos (vim.command(str)yvim.eval(str)), tres constantes (vim.buffers,vim.windowsyvim.current) y un objeto error (vim.error). Las tres constantes mencionadas no son realmente constantes sino variables que pueden ser reasignadas pero, como dice también la ayuda, esto sería absurdo ya que se perdería acceso a los objetos de Vim que referencian. Por otra parte cabe recordar que no existen constantes en Python. Como cita el libro "Inmersión en Python":

Todo puede cambiar si lo intenta con ahínco. Esto se ajusta a uno de los principios básicos de Python: los comportamientos inadecuados sólo deben desaconsejarse, no prohibirse.

Read more »

Tags: , ,

Sincronizar Motorola Q y Evolution con SynCE y MultiSync en Ubuntu Jaunty

Para sincronizar contactos, calendario y tareas entre un Motorola Q y Evolution se necesitan dos programas: SynCE y MultiSync.

Como dice el sitio web de SynCE, el objetivo del proyecto es proporcionar un medio de comunicación entre un dispositivo con Windows Mobile y un equipo con Linux, *BSD u otro UNIX, mediante una conexión USB o Bluetooth. Esto permite utilizar la PC para navegar el sistema de archivos del teléfono e instalar aplicaciones y proporciona el medio de comunicación para que mediante otro programa (MultiSync) sea posible sincronizar contactos, calendario y tareas entre el teléfono y una aplicación PIM de escritorio como Evolution.

MultiSync es una herramienta de software libre que sincroniza calendarios, libretas de direcciones y otra información PIM, entre programas de una PC o de otras PC, dispositivos móviles o teléfonos móviles. Depende del framework de desarrollo OpenSync.

Read more »

Tags: ,

umask

Umask (user mask) es un número octal que UNIX utiliza para determinar qué permisos NO asignar automáticamente a los nuevos archivos y directorios creados. Umask sólo restringe permisos; no concede permisos adicionales más allá de lo especificado por defecto.

La mayoría de los sistemas UNIX especifican el valor octal 666 (rw-rw-rw-) para la creación de archivos y el valor octal 777 (rwxrwxrwx) para la creación de directorios. Luego, el kernel utiliza el valor de umask asignado al usuario como máscara para quitar permisos a los definidos por defecto. Todo proceso tiene su umask, heredado del proceso padre del cual desciende.

Los valores más comunes para umask son 022, 027 y 077.
Normalmente el valor de umask se define en /etc/profile

# Set the user's umask
umask 022

aunque es posible setearlo en algún archivo de sesión como ~/.bash_profile, e incluso manualmente para la sessión actual. En todos los casos se utiliza el comando umask que es una función interna de Bash, y de otros shells como ksh y csh. (Si umask fuera un programa separado del shell no podríamos cambiar el valor de umask del proceso shell actual).

Cálculo de permisos

Los permisos de creación se obtienen realizando un AND binario entre el permiso por defecto y el complemento unario (NOT binario) de umask:

Suponiendo que quisiéramos averiguar los permisos de creación de archivos y directorios para umask 027:

permisos archivos:    666 & ~027
permisos directorios: 777 & ~027

Recordemos que el complemento unario puede obtenerse fácilmente reemplazando ceros por unos y unos por ceros:

Complemento unario:
 027 = 000 010 111
~027 = 111 101 000

Luego de obtener el complemento unario se procede a realizar el AND binario:

Archivos:
 666 = 110 110 110
~027 = 111 101 000
------------------
 640 = 110 100 000  <-- (rw-r-----)
Directorios:
 777 = 111 111 111
~027 = 111 101 000
------------------
 750 = 111 101 000  <-- (rwxr-x---)

En bash

$ umask 027
$ mkdir foo
$ touch bar
$ ls -l
drwxr-x---  2 xleo xleo    48 2009-08-19 01:09 foo
-rw-r-----  1 xleo xleo     0 2009-08-19 01:10 bar

Referencias

Tags: ,

Humor en comentarios de códigos fuente

Visto en stackoverflow.com

Algunos de ellos:

// sometimes I believe compiler ignores
// all my comments
// When I wrote this, only God and I understood
// what I was doing. Now, God only knows
if (/*you*/ $_GET['action']) { //celebrate
//
// Dear maintainer:
//
// Once you are done trying to 'optimize'
// this routine, and have realized what
// a terrible mistake that was, please
// increment the following counter as a
// warning to the next guy:
//
// total_hours_wasted_here = 16
//
// I'm sorry.
virgin = 0; /* you're not a virgin anymore, sweety */
... or die // bitch
// This is crap code but it's 3 a.m. and
// I need to get this working.
// Hard to explain

Tags: ,

Calculadora de notación polaca inversa en C

La Notación Polaca Inversa (RPN en inglés, Reverse Polish Notation) es un método de introducción de datos alternativo al algebráico. Fue creada en 1920 por el matemático polaco Jan Lukasiewicz como una forma de escribir expresiones matemáticas sin tener que utilizar paréntesis y corchetes.

En la notación polaca inversa los operadores suceden a sus operandos. Por ejemplo, la expresión algebráica5+((1+2)*4)-3se traduce a la notación polaca inversa como5 1 2 + 4 * + 3 -.

Su principio es el de evaluar los datos directamente cuando se introducen y manejarlos dentro de una estructura LIFO (Last In First Out), lo que optimiza los procesos a la hora de programar.

El siguiente programa es una implementación en C de una calculadora que funciona con notación polaca inversa. El programa es una versión modificada del que figura en el libro “El lenguaje de programación C” de Kernighan y Ritchie.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
## ************
## @file calc.c
## ************

#include <stdio .h>
#include <stdlib .h>

#define MAXVAL 100
#define NUMBER '0'

/**
 *  Variables
 */

int verbose;
int sp = 0;
double val[MAXVAL];

/**
 *  Prototypes
 */

int toi (char const *s);
void push (double);
double pop (void);

/**
 *  Main
 */

int main (int argc, char const *argv[])
{
  int offset, i;
  double op2;

  if (argc < 2)
  {
    fprintf (stderr,
      "Usage: %s [-v] EXP\n", argv[0]);
    return 1;
  }

  verbose = offset =
    (strcmp (argv[1], "-v") == 0) ? 1 : 0;
 
  for (i = 1 + offset; i < argc; i++)
  {
    switch (toi (argv[i]))
    {
      case '+' :
        if (verbose)
          printf ("operator +\n");
        push (pop() + pop());
        break;
      case '-' :
        if (verbose)
          printf ("operator -\n");
        op2 = pop();
        push (pop() - op2);
        break;
      case '*' :
        if (verbose)
          printf ("operator *\n");
        push (pop() * pop());
        break;
      case '/' :
        if (verbose)
          printf ("operator /\n");
        op2 = pop();
        if (op2 != 0.0)
          push (pop() / op2);
        else
          fprintf (stderr,
            "Error: division by zero\n");
        break;
      case NUMBER :
        push (atof (argv[i]));
        break;
      default :
        fprintf (stderr,
          "Error: unknown operator %s\n",
          argv[i]);
        break;
       
    }
  }

  // Result
  printf ("%f\n", val[0]);

  return 0;

}

/**
 *  Toi
 */

int toi (char const *s)
{
  return (isdigit(s[0])) ?
    NUMBER : (int) s[0];
}

/**
 *  Push
 */

void push (double e)
{
  if (sp < MAXVAL)
  {
    if (verbose)
      printf ("push %f in %d\n", e, sp);
    val[sp++] = e;
  }
  else
    fprintf (stderr, "Error: full stack\n");
}

/**
 *  Pop
 */

double pop (void)
{
  if (sp > 0)
  {
    --sp;
    if (verbose)
      printf ("pop %f from %d\n", val[sp], sp);
    return val[sp];
  }
  else
  {
    fprintf (stderr, "Error: empty stack\n");
    return 0.0;
  }
}
</stdlib></stdio>

Para compilarlo ejecutamos

$ gcc -o calc calc.c

A diferencia del programa original publicado en el libro de C, aquí se utiliza el arrayargv[]como fuente de operandos y operadores. Esto permite que podamos llamar al programa desde línea de comandos de la siguiente manera:

$ ./calc 5 1 2 + 4 \* + 3 -

Notar que para el caso especial del operador multiplicación, debemos escaparlo para evitar la expansión propia de bash antes de la llamada efectiva al programa.

El programa admite la opción-v(verbose), que muestra información acerca de uno de los pasos realizados.

Ejemplo I

$ ./calc -v 5 1 2 + 4 \* + 3 -
push 5.000000 in 0
push 1.000000 in 1
push 2.000000 in 2
operator +
pop 2.000000 from 2
pop 1.000000 from 1
push 3.000000 in 1
push 4.000000 in 2
operator *
pop 4.000000 from 2
pop 3.000000 from 1
push 12.000000 in 1
operator +
pop 12.000000 from 1
pop 5.000000 from 0
push 17.000000 in 0
push 3.000000 in 1
operator -
pop 3.000000 from 1
pop 17.000000 from 0
push 14.000000 in 0
14.000000

Read more »

Tags: ,

Tiempos de ejecución de un proceso

Traducción de What do ‘real’, ‘user’ and ‘sys’ mean in the output of time?

$ time foo
real  0m13.520s
user  0m1.628s
sys   0m1.420s
Real
Tiempo real transcurrido desde que el proceso comienza hasta que finaliza. Esto implica el tiempo utilizado por otros procesos ejecutándose al mismo tiempo y el tiempo en que el proceso se encuentra bloquedo (por ejemplo, esperando que una llamada al sistema como I/O se complete).
User
Tiempo de CPU empleado por el proceso en modo usuario (fuera del núcleo). El tiempo de CPU de otros procesos y el tiempo de espera de una llamada al sistema no cuentan en este valor.
Sys
Tiempo empleado por el núcleo para atender peticiones del proceso. Esto significa tiempo de uso de CPU en llamadas al sistema dentro del núcleo, como por ejemplo I/O.

User+Sys equivale al total de tiempo de CPU usado por el proceso.

Read more »

Tags:

Snippets en Vim con snipMate

Basado en el editor TextMate (mac), snipMate.vim es un plugin que permite insertar pedazos de código de uso frecuente simplemente tipeando parte del mismo y apretando<tab>.

Por ejemplo, supongamos que estamos editando un programa en C y queremos agregar un ciclo for, simplemente escribimosfor<tab>y esto se expandirá en un típico bucle de C

for (i = 0; i < count; i++)
{
    /* code */
}

Inmediatamente después el cursor quedará resaltando la palabra “count” para que podamos reemplazarla por nuestra condición de fin. Un par de tabs más nos llevarán al cuerpo del ciclo donde podremos seguir utlizando snippets comopr<tab>, lo que expandirá en la función printf

for (i = 0; i < 10; i++)
{
    printf("%s\n");
}

snipMate tiene una sintáxis muy sencilla (según dicen, muy similar a la usada por TextMate) lo que permite crear nuestros propios snippets, o mejorar los existentes. La versión actual (0.77) trae una colección de snippets para los siguientes lenguajes: C, Obj-C, C++, Sh, TeX, Java, Ruby, Perl, Python, PHP, JavaScript, y HTML.

El proyecto parace estar bastante activo y la última versión es del 30/03/2009.

Tags:

Accediendo a MySQL con Python y MySQLdb

MySQLdb es un módulo que implementa la API estándar (PEP249) para manejo de bases de datos, en este caso MySQL. MySQLdb es en realidad un wrapper del módulo _mysql que provee Python, el cual implementa la mayoría de las funciones definidas en la API C de MySQL. La idea de definir una API estándar es que uno debería poder cambiar a otra base de datos sin modificar demasiado el código.

This API has been defined to encourage similarity between the Python modules that are used to access databases.

Instalación MySQLdb

El paquete que nos provee el módulo se llama python-mysqldb. Podemos comprobar que lo tenemos instalado mediante

dpkg --get-selections | grep python-mysqldb

Si no existe lo instalamos

sudo apt-get install python-mysqldb

Para comprobar que efectivamente tenemos el módulo disponible y funcionando llamamos al intérprete de Python y escribimos

>>> import MySQLdb

Un silencioso retorno de carro indica que podemos el módulo ha sido cargado. Salimos del intérprete conC-d.

Creación de una DB de pruebas

Para nuestro ejemplo crearemos una base de datos con una única tabla. Entramos a la consola de MySQL y escribimos

mysql> CREATE database test;
Query OK, 1 row affected (0.01 sec)

mysql> USE test;
Database changed
mysql> CREATE TABLE users (
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> name VARCHAR(100),
    -> lastname VARCHAR(100)
    -> );
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO users (name, lastname) VALUES
    -> ('Juan', 'Perez'),
    -> ('Ana Maria', 'Lopez'),
    -> ('Carlos L.', 'Gutierrez');
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

Nuestra tabla users quedó entonces

mysql> SELECT * FROM users;
+----+-----------+-----------+
| id | name      | lastname  |
+----+-----------+-----------+
|  1 | Juan      | Perez     |
|  2 | Ana Maria | Lopez     |
|  3 | Carlos L. | Gutierrez |
+----+-----------+-----------+
3 rows in set (0.00 sec)

Creación del script mysqldb.py

Creamos el siguiente script que se conectará a la base de datos test y traerá todos los datos de los usuarios cargados en la tabla users. En el script es necesario reemplazar <user> y <pass> por los datos requeridos para acceder a tu DB.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/python
import MySQLdb

# Conexion a la base de datos
conn = MySQLdb.connect (host="localhost",
    user="<user>", passwd="<pass>", db="test")

# Creacion cursor
cursor = conn.cursor()

# Ejecucion query
cursor.execute("SELECT * FROM users")

# Manejo de datos devueltos por la consulta
while True:
    row = cursor.fetchone()
    if row is None:
        break
    else:
        print "id: %s\tname: %s %s" %
            (row[0], row[1], row[2])

# Finalizado cursor y objeto conexion
cursor.close()
conn.close()

La salida debería ser

id: 1   name: Juan Perez
id: 2   name: Ana Maria Lopez
id: 3   name: Carlos L. Gutierrez

Links recomendados para ampliar conocimientos

Tags: , ,

GNU gettext con php5

gnu-head-sm

GNU gettext es la biblioteca GNU de internacionalización (i18n) y es usada para escribir programas con interfaz en múltiples idiomas.

wikipedia

La internacionalización es el proceso de diseñar software de manera tal que pueda adaptarse a diferentes idiomas y regiones sin necesidad de cambios de ingeniería ni de código. La localización es el proceso de adaptar el software para una región específica mediante la adición de componentes específicos de un locale y la traducción de los textos, por lo que también se le puede denominar regionalización.

Es una práctica común en el idioma inglés (sobre todo en el ámbito de la computación), abreviar internationalization como “i18n”. Ello se debe a que entre la primera i y la última n de dicha palabra hay 18 letras. Lo mismo sucede con localization, que se abrevia “L10n”.

gettext en PHP

Existen dos formas de usar gettext en PHP: mediante la extensión gettext nativa de PHP o utilizando la librería PHP-gettext, escrita en PHP, que no necesita ninguna extensión.

La forma de usar gettext es

1
2
3
4
# @file: index.php
<?php
print _("hello world");
?>

La función_(), alias degettext(), devolverá la cadena “hello world” en el lenguaje correspondiente, de acuerdo al locale que hayamos seleccionado.

Si ejecutamos el script obtendremos “hello world” como salida, puesto que no hemos indicado ningún locale, ni creado ningún catálogo de traducción. Necesitamos crear el archivocomments.pocon la traducción de la cadena “hello world”. Esto puede hacerse mediante el comandoxgettext, o mediante un editor gráfico como poEdit.

apt-get install poedit

Creación del catálogo con poEdit

Ante todo debemos saber que la estructura de directorios para guardar los distintos catálogos tiene forma determinada. Por cada traducción se utiliza un directorio. Este generalmente se nombra usando dos letras minúsculas para el idioma, un guión bajo y dos mayúsculas para el país (es_AR, para español de Argentina). Dentro de este directorio debe existir otro con el nombre LC_MESSAGES, el cual será finalmente el directorio que contendrá el catálogo.

La estructura de nuestro ejemplo será

.
|-- locale
|   `-- es_AR
|       `-- LC_MESSAGES
|           |-- messages.mo
|           `-- messages.po
`-- index.php

El archivomessages.moes la versión compilada demessages.poy el que usará PHP para obtener la traducción.

Iniciemos poEdit. La primera vez nos preguntará por nuestro nombre y correo electrónico. Estos datos servirán para saber quién fue el último traductor que modificó el catálogo. Una vez en la ventana principal vamos a File -> New catalog. Seleccionamos el idioma (Español en este caso), el país y el código de caracteres tanto para el catálogo como para la fuente de datos desde la que obtendremos el listado de cadenas que requieren traducción. En la solapa Paths debemos colocar en ‘Base path’ la ruta completa al directorio base de archivos php (conteniendo las cadenas mencionadas anteriormente) y, debajo, en el cuadro ‘Paths’ agregar una entrada con ‘.’ indicando que se debe utilizar el directorio ingresado anteriormente. Al hacer click en OK aparecerá un cuadro de diálogo para guardar el nuevo catálogo. Navegamos hasta el directorio LC_MESSAGES y guardamos el archivo como messages.po. poEdit escaneará el directorio ingresado en Paths y extraerá todos las cadenas gettext. Ahora queda simplemente realizar la traducción y guardar el archivo. Al guardarlo, poEdit lo compilará y generará el .mo que finalmente usará PHP.

Ahora, modificaremos index.php para setear el locale

1
2
3
4
5
6
7
8
9
10
11
12
13
# @file: index.php
<?php
$language = 'es_AR.UTF-8';
putenv("LANG=$language");
setlocale(LC_ALL, "");

$domain = "messages";
bindtextdomain($domain, "./locale");
bind_textdomain_codeset($domain, 'UTF-8');
textdomain($domain);

print _("hello world");
?>

Aun nos queda un paso y es comprobar que tengamos dicho locale habilitado en nuestro sistema. Esto lo hacemos mirando la salida de

locale -a

Si no tenemos habilitado el charset es_AR.UTF8, ejecutamos

locale-gen es_AR.UTF-8

En este punto la salida del script debería ser la traducción que hicimos al español.

Tip

Para casos en que la cadena a traducir contenga variables, podemos utilizar esta función:

1
2
3
4
5
6
7
8
9
10
function __($string)
{
    $arg = array();
    for($i = 1 ; $i < func_num_args(); $i++)
        $arg[] = func_get_arg($i);
    return vsprintf(gettext($string), $arg);
}

$total = 400;
print __("results %d - %d of about %d", 1, 20, $total);

La traducción podría quedar

resultados %d - %d de aproximadamente %d

Tags: , , ,