Switch from High Frequency 2vCPU/2GB ($18/mo) to Regular Performance
1vCPU/1GB ($5/mo). Tighten HAProxy rate limits from 100/50/30 RPM to
60/30/15 RPM with lower global RPS thresholds (200/300 instead of
300/500). Reduce maxconn, socket buffers, file descriptor limits, and
Docker resource settings to fit within 1GB RAM constraints. Update all
user-facing content and translations across 14 locales.
Replace the cdnjs CDN dependency with locally hosted font files and a
minimal CSS file containing only the 4 icons used (check, minus,
magnifier, social-github). Remove the CDN preconnect links.
Also reduce the gap between the IP display and the info table divider,
fix vertical alignment of icon and text rows by switching to
vertical-align: middle, and override the icon line-height to match
table text.
When the primary IP lacks city data, the conditional <tr data-label="city">
row is absent from the DOM, so readLabels() cannot scrape the translated
label. Embed translated labels as data-l-* attributes on the <table> element
so they are always available regardless of which rows are rendered.
- Remove disruptive alert() on error pages, rely on role="alert" banner
- Add skip navigation link for keyboard users (WCAG 2.4.1)
- Fix heading hierarchy on error page (sr-only status text in h1)
- Add "opens in a new tab" screen reader hint on external links
- Wrap language selector in form with noscript submit button (WCAG 3.2.2)
- Add aria-label to copy-to-clipboard buttons and footer landmarks
- Use h2 for alt-IP section to maintain single h1 per page
- Add persistent underline on links for non-color identification (WCAG 1.4.1)
- Fix footer attribution contrast ratio to ~5.5:1 (WCAG 1.4.3)
- Add aria-busy/role=status loading announcements for skeleton state
- Add translations for new accessibility strings across all 14 locales
- Update integration tests to assert role="alert" instead of alert()
Rewrite script.js from ES5 (IIFEs, var, .then() chains) to modern ES2022+:
const/let, arrow functions, template literals, async/await, optional
chaining, for...of, .includes(), AbortSignal.timeout(), and DOM APIs
for dynamic HTML construction. Fix unhandled clipboard rejection, abort
timeout leak, and global scope pollution. Update script tag to
type="module" and fix CSS: top -> inset-block-start, flex-start -> start,
and indentation in media query block.
Replace all {{ t_field_name }} template variables with {{ "msgid"|t(locale) }}
filter calls across all 6 template files. This works with the #[askama::filter_fn]
defined in handlers/html.rs, which calls i18n::translate() directly.
Templates updated: index.html, error_alert.html, error_page.html,
_footer.html, _footer_lang.html, _search_form.html
The JS used fragile numeric indices to look up localized labels from the
server-rendered table. When optional rows (ASN, Org, Country, City) were
missing for the primary IP, the indices shifted, causing 'Proxy' to appear
as the City label. Add data-label attributes to each <tr> in the template
and key the label map by name instead of position.
The detectAltIp() function was unconditionally fetching the viewer's own
IPv6/IPv4 address and displaying it alongside queried IP results. Pass
the is_query flag to the client via a data attribute and guard the
alt-IP detection so it only runs when viewing your own IP.
- Add i18n module with Locale enum, PO parser, and Accept-Language negotiation
- Create gettext .po translation files for en, es, de, ja, ko, id, fr, ru, pt, it, zh-Hant, zh-Hans, nl, ar
- Route HTML pages under /{lang}/ prefix using IETF language tags
- Auto-detect browser language and redirect from / to /{lang}/
- Add language selector dropdown in footer
- Localize geo names (country, city) from MMDB where available
- Add hreflang alternate links and multilingual sitemap for SEO
- Support RTL layout for Arabic
- Update integration tests for new routing structure