Ir para o conteúdo

5. Idioma e tempo relativo

Toda tela de lista mostra quando algo aconteceu: "há 3 minutos", "ontem", "em 2 dias". Feito na mão, esse texto erra sempre nos mesmos lugares — "há 1 minutos", "há 0 segundos", "há 60 minutos" — e cada tela erra de um jeito.

O core resolve isso com relative_time, que fala a mesma língua do resto do app: o mesmo Locale que você já passa para translate. 🌍

O Locale e o translate

O Locale é contexto de entrada, como o Theme: a view lê, ele não vira nó da árvore. O translate (apelidado t) procura a chave na tabela do idioma e interpola com str.format:

from tempest_core import Locale, translate

textos = {
    "pt": {"ola": "Olá, {nome}"},
    "en": {"ola": "Hello, {nome}"},
}

print(translate("ola", Locale(language="pt"), textos, nome="Ana"))
print(translate("ola", Locale(language="en"), textos, nome="Ana"))
print(translate("tchau", Locale(language="pt"), textos))
Olá, Ana
Hello, Ana
tchau

Chave ausente devolve a própria chave — a tela degrada para o texto do desenvolvedor em vez de quebrar.

relative_time: um delta, uma frase

relative_time recebe um delta em segundos (alvo - agora): negativo é passado, positivo é futuro.

from tempest_core import Locale, relative_time

pt = Locale(language="pt", region="BR")
en = Locale(language="en")

for delta in (-1, -45, -90, -3_600, -86_400, 172_800):
    print(f"{delta:>8} | {relative_time(delta, pt):<14} | {relative_time(delta, en)}")
      -1 | agora          | now
     -45 | há 45 segundos | 45 seconds ago
     -90 | há 2 minutos   | 2 minutes ago
   -3600 | há 1 hora      | 1 hour ago
  -86400 | ontem          | yesterday
  172800 | em 2 dias      | in 2 days

Sem locale, vale o Locale() padrão, que é português: relative_time(-45) devolve "há 45 segundos".

Por que segundos, e não datetime?

O core é puro e determinístico: ele não lê relógio. "Agora" é decisão do app — no browser é o relógio do cliente, no desktop é outro. Receber o delta deixa a função testável sem congelar tempo.

Com datetime, o delta é uma subtração:

from datetime import datetime, timezone

from tempest_core import Locale, relative_time

enviado_em = datetime(2026, 9, 27, 14, 0, tzinfo=timezone.utc)
agora = datetime(2026, 9, 27, 14, 3, 10, tzinfo=timezone.utc)

print(relative_time((enviado_em - agora).total_seconds(), Locale(language="pt")))
há 3 minutos

As regras, escritas

  1. Janela do "agora": menos de 30 segundos, para qualquer lado — zero incluído — é "agora" / "now". Nunca "há 0 segundos".
  2. Unidade: segundos abaixo de 1 minuto, minutos abaixo de 1 hora, horas abaixo de 1 dia, dias abaixo de 1 semana, semanas abaixo de 30 dias, meses abaixo de 365 dias, e anos daí em diante.
  3. Arredondamento: meio para cima, como o Math.round do JavaScript. Então 89 s é "1 minuto" e 90 s é "2 minutos".
  4. Arredondou até o limite, sobe de unidade: 59,5 s vira "1 minuto", nunca "60 segundos"; 23,5 h vira "ontem", nunca "24 horas".
  5. Singular só quando a quantidade é exatamente 1.

Mês é 30 dias, ano é 365

A conta não conhece calendário, de propósito: o resultado depende só do delta, e sai idêntico no tempestweb e no tempestroid. Pelo mesmo motivo, "ontem" quer dizer "um dia de 24 horas atrás", não "o dia anterior do calendário".

nan e infinito são recusados

relative_time(float("nan")) levanta ValueError. Um delta que não é número é dado ruim chegando de fora, e "há inf anos" na tela só esconderia isso.

Outro idioma, ou outro texto

As frases vêm de RELATIVE_TIME_TRANSLATIONS e passam pelo mesmo translate. Para acrescentar um idioma ou trocar uma frase, passe translations com as chaves que quiser:

from tempest_core import Locale, relative_time

es = {"es": {"relative_time.past.minute.other": "hace {n} minutos"}}

print(relative_time(-180, Locale(language="es"), es))
print(relative_time(-3_600, Locale(language="es"), es))
hace 3 minutos
1 hour ago

A chave que a sua tabela não trouxe cai no inglês (RELATIVE_TIME_FALLBACK_LANGUAGE), uma a uma — nunca vaza a chave crua. Idioma sem tabela nenhuma também cai no inglês.

As chaves

relative_time.now, e relative_time.<past|future>.<unidade>.<one|other>, com <unidade> em second, minute, hour, day, week, month, year. O {n} é a quantidade já arredondada.

Recapitulando

  • relative_time(delta, locale) recebe alvo - agora em segundos e responde no idioma do Locale do app.
  • Menos de 30 s é "agora"; arredondamento meio para cima; arredondou até o limite, sobe de unidade.
  • translations acrescenta idioma ou troca frase; o que faltar cai no inglês.
  • nan/infinito levantam ValueError.