Skip to content

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 25 Oct 19:06
· 166 commits to main since this release

Added

  • Sequenced realtime frames. A Logic that also implements realtime.Framer frames every payload admitted to each member of its room with a per-connection sequence: 1 for the join snapshot, then 2, 3 and so on, restarting at 1 on reconnect. The host owns the wire format. Join fails with realtime.ErrEmptyFrame (or the wrapped Frame error) when the framed snapshot is empty or fails, and the failure does not close the existing connection or admit the new peer. Admitted sends from Snapshot keep their sequence and are not rolled back; a send whose Frame errors or overflows the queue closes the existing connection, which stays closed. A Logic that does not implement Framer is unchanged. See the realtime guide.

  • Google Cloud Storage adapter (experimental, separate module). storage/gcs is a storage.Store on Google Cloud Storage, in its own Go module so no cloud SDK reaches a project that does not import it. gcs.New authenticates with application-default credentials, workload identity or explicit credentials, and gcs.NewFromClient takes a client the host built. Writes are create-only and atomic, and a retried write that already committed is confirmed by a per-write token, checksum and size rather than reported as a collision. Experimental: tested against an in-process emulator, not yet against a real bucket. The module is versioned and tagged separately as storage/gcs/vA.B.C, after the root v0.7.0 tag it requires. See the uploads guide.

  • Admin bulk delete. admin.Options.BulkDelete lets a staff account delete the checked rows of one changelist page after a confirmation dialog. The delete is one all-or-nothing transaction that cascades like a single-row delete, an invalid selection (empty, over a page, a wrong count, a repeated or non-canonical key) is rejected whole, and each committed delete logs one admin.EventBulkDelete (tango.admin.bulk_delete) event with model, selected_count (the rows selected, not the database rows a cascade deleted) and username. It is off by default, needs JavaScript and never spans more than a page. See admin registration.

  • Grouped filters. Query.Filter is an optional boolean tree ANDed with Where and Any, built with db.Cond, db.And, db.Or and db.Not (the Expr and ExprOp types). A Query without a Filter produces exactly the SQL it did before. A filter holds at most MaxFilterNodes (100) nodes and MaxFilterDepth (8) levels.

  • IN, IS NULL and IS NOT NULL. OpIn with db.In(field, slice) (a nil or empty list matches nothing on every dialect; at most MaxInValues, 1,000, values per list and MaxQueryArgs, 10,000, bind values per query; a zero time.Time is rejected), and OpIsNull/OpIsNotNull with db.IsNull/db.IsNotNull, which take no value. All three work in Where, Any and Filter. A foreign key left at zero is stored as NULL, so OwnerID = 0 still never matches (unchanged); use IsNull for unset keys.

  • Store.First and Store.Exists. First reads one row (primary-key order unless OrderBy is set, ErrNotFound naming the model when none, the destination untouched on any error); Exists reports whether any row matches. They validate and build SQL exactly as List and Count do.

  • Typed query errors. ErrInvalidQuery, ErrQueryLimit, *QueryError and *QueryLimitError. Every malformed query is refused before any SQL runs. The existing Where/Any validation errors keep their text and now also match ErrInvalidQuery.

  • Locale resolvers. i18n.Resolver (a locale source that also names the request headers it depends on), the helpers FromQuery, FromCookie and FromHost, Chain (the host's order, then Accept-Language), FuncResolver for a plain function, and MiddlewareResolver to install one. Each helper returns only a locale with a registered catalog and none sets a cookie. Middleware and DefaultLocaleResolver are unchanged.

  • i18n/i18ntest.Isolate. Test support: i18ntest.Isolate(t) gives a test an empty i18n state (catalogs, the OnMissing hook and its once-state, the observed keys) and restores the previous state in t.Cleanup. A test that uses it must not be parallel.

  • i18n.Keys and i18n.Validate. T records each (key, fallback) pair the first time it runs with a resolved locale (nothing is recorded, and nothing allocated, when no locale is set). Keys() lists them; Validate() checks every catalog against them and returns a Report with Problems (ProblemMismatch, ProblemEmpty, ProblemConflictingFallback when a key is seen with two fallbacks, ProblemRegistryFull) and the advisory, sorted Unobserved catalog keys. It covers only what has run in this process, and the registry holds at most 4,096 keys, so fallbacks must be stable literals.

  • i18n.OnMissing. A development hook that reports a missing translation (an absent key, an empty value or a value whose format verbs do not match the fallback's) with the resolved locale and key, at most once per pair. It is nil by default, runs outside the package's locks, and OnMissing(nil) clears it.

  • The admin's remaining English is translatable. <html lang> follows the selected catalog on every admin page and on the Markdown preview document (en when none is selected). The Markdown and JSON widgets' tab labels, buttons, character count, aria-labels, iframe title, script messages (Nothing to format., Preview failed. and the rest, delivered as data-msg-* attributes only when a locale was resolved), and the JSON validation errors now use admin.* keys with the current English as fallback. An app without i18n renders the same output as before. A test keeps new literal text out of admin's templates.

  • Translatable accounts pages. The register, login, password-reset and email-verification pages and their form errors (including the generic login error, which stays one message for every failure) go through i18n.T under accounts.* keys with the current English as fallback. A page declares <html lang> only when a catalog was selected, so an app without i18n renders exactly what it did before. The JSON API's error text stays English; clients branch on code.

  • Accounts emails in the request's language. The verification and reset emails are rendered in the locale of the request that queued them (accounts.email.* keys, English fallback). The locale is captured when the email is queued, before any account lookup; the queued job keeps nothing else of the request. A translated body that lacks the %s link placeholder falls back to the English body, so the link is never dropped.

  • The tango newproject scaffold points at the i18n guide. The generated middleware list carries a two-line comment saying how to opt in (i18n.Middleware(nil) and catalogs). The generated project is otherwise unchanged.

  • examples/api-with-admin in Spanish. The example serves the admin in Spanish end to end: a complete es catalog, a small es-MX override, a ?lang= → cookie → Accept-Language order and a language switcher, with a test that walks the pages in both locales and checks OnMissing and Validate. tanGO itself still ships no catalogs.

  • examples/accounts-mail in Spanish. Registering, confirming an address and resetting a password run in Spanish, emails and links included, with a test that walks the flow and fails on any missing key.

Changed

  • The i18n middleware sets Vary and Content-Language. With i18n.Middleware(nil) every response gets Vary: Accept-Language, so a cache in front of the app no longer serves one language to another visitor; MiddlewareResolver(Chain(...)) sends the tokens its resolvers need (Cookie for FromCookie). Content-Language names the selected catalog's locale, and only when a catalog was selected. Existing Vary values, including ones a downstream handler adds, are merged without repeating a token (ignoring case). A resolver passed as a plain function adds no Vary (use FuncResolver to declare one). Nothing changes for an app without the middleware.

  • A bad catalog value falls back instead of printing garbage. i18n.T now ignores a catalog value that is empty or whose format verbs differ from the fallback's (positional verbs such as %[2]s count, so reordering arguments is still fine), tries the base language, and then shows the fallback. Before, "Nuevo" for "New %s" rendered Nuevo%!(EXTRA string=Pet) and an empty value rendered nothing. A literal % in a fallback must be %% once arguments are passed.

  • Upgrade note: literal % in i18n fallbacks. The fallback is a format string whenever arguments are passed, so a bare % is read as a format verb and must be escaped. Change i18n.T(ctx, "sale.label", "Save 10% on %s", item) to i18n.T(ctx, "sale.label", "Save 10%% on %s", item). Search the project's i18n.T calls and catalogs for % followed by a letter or space before relying on the new fallback rule.

Fixed

  • Locale matching ignores case and the -/_ separator. Accept-Language: es-mx, ES-MX and es_MX now select a catalog registered as es-MX (before, es-mx skipped the regional catalog and ES-MX matched nothing, not even es). The locale the middleware stores is the catalog's own spelling, and catalogs registered under two spellings of one locale are merged.