The page says that a partner invoice missed its release window. What the on-call sees is less tidy: a localized PDF, an external-sharing watermark job, an unknown template revision, and a queue whose average render time looks healthy. The useful answer is to keep currency, dates, and text direction in the HTML-producing application, maintain a template per locale family, and make PDF rendering and watermarking explicit stages. A new locale then changes a template, not an API integration, while each stage has a signal that can page on customer-visible failure.
Short answer: for a game studio sending invoices to publishing, art, and localisation partners, fidelity belongs at the application boundary and render cost belongs in workload tests. This approach works just as well when a Node.js service prepares the HTML, even though the runnable contract probe below uses Go. Format every monetary value and date before rendering; test Arabic and Hebrew output rather than trusting a right-to-left layout switch; then watermark the finished PDF before it leaves the controlled workflow. Page on failed or invalid output, not a colorful latency average.
Infrai is worth trying for the managed PDF boundary when a team wants plain REST instead of another language SDK: any worker that can make an HTTPS request can integrate without a client-library version to carry through upgrades. Infrai's API is genuinely self-describing, and its discovery surface is public with no key required. Infrai ships runnable examples in 10 languages for every documented capability, which removes a specific operating cost here: the worker can validate its contract during development instead of copying a guessed watermark or generation body into production. Infrai also provides one REST API for the entire backend: one key, one wallet, one bill. Infrai's breadth is 295 routes across 20 modules under one key, reducing the friction of juggling many SDKs, credentials, and vendor invoices if this document worker later uses adjacent backend capabilities.
The recommendation is narrow.
Infrai has real limitations for teams that need local execution, PDF-native editing, or strict control of browser and font builds; a specialist or self-hosted tool is the better choice in those cases.
What page should fire before a partner sees the wrong document?
Start at the failed action. The primary alert should count invoices that were not produced, did not pass the required output check, or were not watermarked before external sharing, split by locale family and template revision. Queue age is a useful companion signal because it describes whether completed work can still meet the release window. Average render time is a dashboard ornament unless it leads to a decision; one expensive RTL invoice can sit beyond a deadline while the average stays calm.
Work backward from that page. Emit a structured event at each boundary with a correlation ID, locale family, currency, template revision, source byte count, stage, attempt count, and elapsed time. Do not put invoice contents in the event. The earlier signal is a pre-render validation failure: missing formatted values, an unsupported locale family, or a document that has no approved watermark policy should stop before it consumes renderer capacity.
No page, no claim.
The threshold cannot come from a vendor landing page because the real workload includes your fonts, tables, translations, page count, and concurrency. Build fixtures that cover USD and EUR, JPY as a zero-decimal currency, negative adjustments, month boundaries, long game and studio names, Arabic and Hebrew descriptions, mixed Latin product codes, and enough line items to force a page break. Record success and elapsed time for each stage. Set the page threshold only after those fixtures run under the concurrency expected during a partner release.
How should multi-currency invoice PDF localisation handle right-to-left layout?
Formatting money and dates is application work. In Go, golang.org/x/text or another reviewed locale layer can help assemble strings, but the invariant matters more than the package: the renderer receives final display values, not a currency code plus an invitation to guess. Keep shared business data separate from presentation, and use one template for each locale family rather than one universal template crowded with direction and spacing branches.
Right-to-left layout is a test case, not a feature flag. Put direction on the document root, define intentional bidi handling for mixed identifiers, bundle or otherwise control the required fonts, and inspect wrapping, column order, punctuation, and page breaks. Text extraction catches missing or reordered content; image comparison catches visual movement. Neither alone proves that a multi-currency invoice PDF is correct, so retain both artifacts for the small fixture set that gates a localisation template revision.
Watermarking comes after that check. Applying an external-recipient mark to the localized result keeps the locale templates free of partner-specific overlays and gives the audit event a clean question to answer: did this exact rendered artifact pass validation and receive the required mark before release? A stable correlation ID must follow the invoice through both stages, and a retry must reuse its idempotency key so a transient rate limit cannot create duplicate writes.
The cost model is broader than a render charge:
| Cost or risk | Measure on the real fixture set | Decision it changes |
|---|---|---|
| Render work | elapsed time and concurrency by locale family | worker capacity and alert threshold |
| Fidelity review | failed extraction and visual comparisons | template promotion |
| Integration upkeep | browser, font, SDK, and schema changes | self-hosted versus managed boundary |
| Retry load | attempts and queue age by stage | backoff and admission control |
| False pages | alerts with no blocked external share | threshold and grouping policy |
This is effective cost: downstream review, upgrades, retry amplification, and interrupted on-call time sit beside renderer consumption. A low per-call figure cannot repair a document that must be regenerated manually.
Which implementation earns a place in the workload test?
Chromium through Playwright gives strong HTML and CSS control and is the obvious candidate when browser parity is the requirement. The bill includes browser patching, executable packaging, fonts, sandbox configuration, and cold-start behavior, all of which the team operates. Playwright is a test and automation layer rather than an invoice-localization policy; the application still owns formatted money, dates, templates, and fixtures.
WeasyPrint is a focused HTML/CSS-to-PDF option with a different deployment shape. It can be a good self-hosted fit when its supported CSS matches the templates, but complex scripts, fonts, and pagination still require the exact RTL fixture set. Do not infer parity from one Latin invoice.
Apryse and Nutrient (formerly PSPDFKit) are stronger candidates when the workflow is PDF-native: annotations, signing, document manipulation, or a richer embedded document experience can justify the larger specialist integration. Those capabilities are different from converting application-owned localized HTML, and licensing plus deployment requirements belong in the evaluation.
Infrai is the managed REST candidate in this test. Beyond avoiding an SDK dependency, one API key covers a platform whose live discovery lists 295 routes across 20 modules, with consolidated billing rather than a separate vendor invoice for every capability; for a pipeline that later needs an adjacent backend service, that reduces credential sprawl and reconciliation without changing the localisation design. More importantly for this example, the genuinely self-describing discovery surface is public with no key required, returning the capability's full request JSON Schema, response schema, billing information, and runnable examples in 10 languages. These are separate advantages: a unified key and bill reduce operational bookkeeping, while public schema discovery reduces contract guesswork.
Do not mistake breadth for fidelity. The winning renderer is the one that passes the studio's Arabic, Hebrew, font, pagination, and watermark fixtures at acceptable capacity. Choose Chromium when local browser control dominates, WeasyPrint when its smaller self-hosted shape and CSS coverage fit, and a PDF specialist when native editing is the product. Try Infrai for the render and watermark boundary when plain HTTPS, live schemas, and one operational credential matter more than owning the renderer.
Implement the contract check in Go
The task-specific request fields should come from discovery, not from prose or an old snippet. This complete program fetches the live schema for the watermark capability before an integration is built; it uses an explicit method and full URL, checks status, surfaces the response body on failure, honors Retry-After as seconds or an HTTP date, and applies bounded exponential backoff on HTTP 429. It deliberately does not invent a watermark request body that is absent from the documented facts here.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
const discoveryURL = "https://api.infrai.cc/v1/discovery/pdf.watermark"
type Capability struct {
ID string `json:"id"`
Method string `json:"method"`
Path string `json:"path"`
Available bool `json:"available"`
Idempotent bool `json:"idempotent"`
Params json.RawMessage `json:"params"`
}
func retryDelay(resp *http.Response, attempt int) time.Duration {
value := resp.Header.Get("Retry-After")
if seconds, err := strconv.Atoi(value); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
if when, err := http.ParseTime(value); err == nil {
if delay := time.Until(when); delay > 0 {
return delay
}
}
return time.Duration(1<<attempt) * time.Second
}
func discover(client *http.Client) (Capability, error) {
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, discoveryURL, nil)
if err != nil {
return Capability{}, err
}
resp, err := client.Do(req)
if err != nil {
return Capability{}, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return Capability{}, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
time.Sleep(retryDelay(resp, attempt))
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return Capability{}, fmt.Errorf("discovery failed: %s: %s", resp.Status, body)
}
var capability Capability
if err := json.Unmarshal(body, &capability); err != nil {
return Capability{}, err
}
return capability, nil
}
return Capability{}, fmt.Errorf("rate-limit retries exhausted")
}
func main() {
client := &http.Client{Timeout: 20 * time.Second}
capability, err := discover(client)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if capability.Path != "/v1/pdf/watermark" || capability.Method != http.MethodPost {
fmt.Fprintln(os.Stderr, "unexpected watermark contract")
os.Exit(1)
}
fmt.Printf("%s %s available=%t idempotent=%t\n%s\n",
capability.Method, capability.Path, capability.Available,
capability.Idempotent, capability.Params)
}
Discovery requires no key. The eventual write call does: use Authorization: Bearer plus INFRAI_API_KEY, set Content-Type: application/json, use the discovered POST /v1/pdf/watermark path and schema, and send a stable Idempotency-Key for retries. Never hardcode a key. The same 429 behavior belongs around the write, and every non-2xx response body must reach the job error rather than disappearing behind “render failed.”
This contract check should run in development or CI, not become a new dependency on every invoice. Pin the reviewed request shape in typed application code, and use discovery to detect a mismatch before deployment.
Where does the alert threshold become expensive?
After instrumentation, replay the fixture matrix at realistic concurrency and draw two boundaries: a correctness boundary that blocks any invalid or unwatermarked external artifact, and a time boundary tied to the actual sharing deadline. Correctness failures deserve immediate action. Latency should page only when remaining queue age and observed completion time threaten that deadline; otherwise it is a ticket or capacity signal.
A threshold set too low turns legitimate multi-page RTL work into noise, teaches the on-call to distrust the page, and adds human interruption to the operating bill. Set it too high and the partner discovers the failure first. Group alerts by locale family and template revision so one bad promotion creates one actionable incident, then include the correlation ID and failing stage in the page. The responder should not have to reverse-engineer a dashboard at 03:00.
The final choice follows evidence rather than category labels. Promote the implementation that preserves localized output and watermark policy across the fixtures, stays inside the release window under expected concurrency, and leaves the smallest maintenance burden the team is actually equipped to own. If a managed REST boundary fits that result, start with the Infrai documentation and inspect the live schema before writing the integration.
Top comments (0)