Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,38 +345,60 @@ To be released.
document/media classes expose `digestMultibase` for external resource
integrity metadata.
[[#830], [#928]]

- Updated [FEP-fe34] cross-origin checks to understand cryptographic origins
for [FEP-ef61] portable ActivityPub IDs and DID URLs. Generated property
accessors and `lookupObject()` now treat `ap:`/`ap+ef61:` IDs and matching
`did:key` verification method IDs as same-origin when their DID components
match.
[[#829], [#926]]

- Added support for [FEP-ef61] portable ActivityPub IRIs in generated
vocabulary codecs. `ap:` and `ap+ef61:` values with decoded or
percent-encoded DID authorities now parse as `URL` objects, and JSON-LD
serialization emits canonical `ap+ef61:` values with decoded DID
authorities.
[[#826], [#850]]

- Added vocabulary support for [FEP-7aa9], including
`FeaturedCollection`, `FeaturedItem`, `FeatureRequest`, and
`FeatureAuthorization`, plus actor `featuredCollections` and
`InteractionPolicy.canFeature` properties.
[[#810], [#914]]

- Added the `Endpoints.uploadMedia` property, the standard ActivityStreams
endpoint for the [ActivityPub Media Upload extension].
[[#754], [#927]]

- Fixed the CommonJS vocabulary build so it no longer requires
`@js-temporal/polyfill` at runtime. The build now bundles
`temporal-polyfill`, while type declarations rely on the standard
`esnext.temporal` lib reference.
[[#823], [#925]]

- Added vocabulary support for the [FEP-22cd] draft, associating each
translated version with its translators, source object, and optional source
review timestamp. [[#1037], [#1038]]

- Added `Translation` class with `id`, `language`, `original`,
`sourceUpdated`, `basis`, `url`, and `urls` properties.
- Added `Translation.getTranslator()`/`Translation.translatorId` and
`Translation.getTranslators()`/`Translation.translatorIds` for
accessing credited actors. The constructor accepts `translator`
and `translators` values.
- Added `Object.translations` property, inherited by `Article`,
`Note`, and other object types, for per-language translation
metadata without creating separate posts.

[FEP-22cd]: https://w3id.org/fep/22cd
[#810]: https://github.com/fedify-dev/fedify/issues/810
[#826]: https://github.com/fedify-dev/fedify/issues/826
[#830]: https://github.com/fedify-dev/fedify/issues/830
[#850]: https://github.com/fedify-dev/fedify/pull/850
[#914]: https://github.com/fedify-dev/fedify/pull/914
[#928]: https://github.com/fedify-dev/fedify/pull/928
[#1037]: https://github.com/fedify-dev/fedify/issues/1037
[#1038]: https://github.com/fedify-dev/fedify/pull/1038

### @fedify/vocab-runtime

Expand Down Expand Up @@ -415,6 +437,8 @@ To be released.
error pages surface as document loading failures with the response URL and
content type, rather than generic JSON parser crashes.
[[#912], [#913]]
- Added <https://w3id.org/fep/22cd> to preloaded JSON-LD contexts.
[[#1037], [#1038]]

[#828]: https://github.com/fedify-dev/fedify/issues/828
[#831]: https://github.com/fedify-dev/fedify/issues/831
Expand All @@ -423,6 +447,14 @@ To be released.
[#924]: https://github.com/fedify-dev/fedify/pull/924
[#935]: https://github.com/fedify-dev/fedify/pull/935

### @fedify/vocab-tools

- Added the `extraContext` property schema option to include a JSON-LD
context only when its terms are used, preserving existing output for
objects without the extension. [[#1037], [#1038]]
- Added the `trustEmbeddedObjects` type schema option so embedded metadata
identifiers need not establish trust in linked actors. [[#1037], [#1038]]


Version 2.3.7
-------------
Expand Down
4 changes: 4 additions & 0 deletions FEDERATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Supported FEPs
- [FEP-5711][]: Inverse Properties for Collections
- [FEP-044f][]: Consent-respecting quote posts
- [FEP-7aa9][]: Featuring recommendations using a dedicated collection
- [FEP-22cd][]: Attributing translations
- [FEP-0837][]: Federated Marketplace
- [FEP-ae0c][]: Fediverse Relay Protocols: Mastodon and LitePub

Expand All @@ -55,6 +56,7 @@ Supported FEPs
[FEP-5711]: https://w3id.org/fep/5711
[FEP-044f]: https://w3id.org/fep/044f
[FEP-7aa9]: https://w3id.org/fep/7aa9
[FEP-22cd]: https://w3id.org/fep/22cd
[FEP-0837]: https://w3id.org/fep/0837
[FEP-ae0c]: https://w3id.org/fep/ae0c

Expand Down Expand Up @@ -228,6 +230,7 @@ fediverse.
- [`PropertyValue`] (Schema.org)
- [`Relationship`]
- [`Source`]
- [`Translation`] ([FEP-22cd]; embedded metadata)

[`Emoji`]: https://jsr.io/@fedify/vocab/doc/~/Emoji
[`Hashtag`]: https://jsr.io/@fedify/vocab/doc/~/Hashtag
Expand All @@ -238,6 +241,7 @@ fediverse.
[`PropertyValue`]: https://jsr.io/@fedify/vocab/doc/~/PropertyValue
[`Relationship`]: https://jsr.io/@fedify/vocab/doc/~/Relationship
[`Source`]: https://jsr.io/@fedify/vocab/doc/~/Source
[`Translation`]: https://jsr.io/@fedify/vocab/doc/~/Translation

### Cryptographic types

Expand Down
7 changes: 7 additions & 0 deletions changes.d/vocab-runtime/translation-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
links:
'#1037': https://github.com/fedify-dev/fedify/issues/1037
'#1038': https://github.com/fedify-dev/fedify/pull/1038
---
- Added <https://w3id.org/fep/22cd> to preloaded JSON-LD contexts.
[[#1037], [#1038]]
10 changes: 10 additions & 0 deletions changes.d/vocab-tools/conditional-contexts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
links:
'#1037': https://github.com/fedify-dev/fedify/issues/1037
'#1038': https://github.com/fedify-dev/fedify/pull/1038
---
- Added the `extraContext` property schema option to include a JSON-LD
context only when its terms are used, preserving existing output for
objects without the extension. [[#1037], [#1038]]
- Added the `trustEmbeddedObjects` type schema option so embedded metadata
identifiers need not establish trust in linked actors. [[#1037], [#1038]]
20 changes: 20 additions & 0 deletions changes.d/vocab/translation-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
links:
'#1037': https://github.com/fedify-dev/fedify/issues/1037
'#1038': https://github.com/fedify-dev/fedify/pull/1038
---
- Added vocabulary support for the [FEP-22cd] draft, associating each
translated version with its translators, source object, and optional source
review timestamp. [[#1037], [#1038]]

- Added `Translation` class with `id`, `language`, `original`,
`sourceUpdated`, `basis`, `url`, and `urls` properties.
- Added `Translation.getTranslator()`/`Translation.translatorId` and
`Translation.getTranslators()`/`Translation.translatorIds` for
accessing credited actors. The constructor accepts `translator`
and `translators` values.
- Added `Object.translations` property, inherited by `Article`,
`Note`, and other object types, for per-language translation
metadata without creating separate posts.

[FEP-22cd]: https://w3id.org/fep/22cd
209 changes: 209 additions & 0 deletions docs/manual/pragmatics.md
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,215 @@ Mastodon:
![Screenshot: An article object with a title, canonical link, and hashtag in
Mastodon](pragmatics/mastodon-article.png)

### Translation metadata

`Translation` records credit and source freshness for one translated language
in an object's `contentMap`, `nameMap`, or `summaryMap`. `Object.translations`
is an array of these entries, inherited by `Article`, `Note`, and other object
types. The text stays in the language maps. Every language shares the same
object ID, replies, and reactions.

> [!WARNING]
> This implements [FEP-22cd].
> The proposal may change. These APIs describe the draft's wire format;
> they do not make other servers display translation credits. Downstream
> applications should pin a Fedify revision containing this support when
> testing the draft.

#### Publishing a translation

An author translating their own article keeps their author attribution and
adds a separate credit for the translated language:

~~~~ typescript twoslash
import { Article, LanguageString, Translation } from "@fedify/vocab";

const articleId = new URL("https://example.com/articles/1");
const authorId = new URL("https://example.com/users/alice");
const sourceUpdated = Temporal.Instant.from("2026-09-01T00:00:00Z");
const article = new Article({
id: articleId,
attribution: authorId,
updated: sourceUpdated,
contents: [
"Original text", // Untagged fallback for readers without language selection.
new LanguageString("Original text", "en"),
new LanguageString("번역문", "ko"),
],
translations: [
new Translation({
language: new Intl.Locale("ko"),
translator: authorId,
original: articleId,
sourceUpdated,
url: new URL("https://example.com/articles/1/ko"),
}),
],
});

const json = await article.toJsonLd();
~~~~

Fedify adds the preloaded `https://w3id.org/fep/22cd` context when translation
metadata needs it. Objects without this metadata keep their existing output.
If you supply an explicit context to
`toJsonLd({ format: "compact", context: ... })`, include the FEP context to get
its compact term names.

| Property | Value | Meaning |
| --------------- | ----------------------- | ------------------------------------------------------ |
| `language` | `Intl.Locale` | Language of this translation. |
| `translators` | Actor objects or `URL`s | One or more translators or reviewers, in no set order. |
| `original` | `URL` | ID of the containing object. |
| `sourceUpdated` | `Temporal.Instant` | Source timestamp at the last human review. |
| `basis` | `URL` | Optional public revision resource used for the review. |
| `urls` | `URL`s or `Link`s | Optional links to this language's rendering. |

The constructor also accepts singular `translator` and `url` values. The FEP
requires `inLanguage` (`language` in Fedify), at least one translator, and
`translationOfWork` (`original` in Fedify).
Applications must check that the language matches their language maps and that
`original` matches the containing object. As with other vocabulary
classes, Fedify does not enforce these relationships or required fields.
Invalid language tags or timestamps can make `fromJsonLd()` reject the whole
containing object; applications should handle parsing errors at their normal
input boundary.

A directly authored language has no `Translation` entry. A metadata entry may
have an `id`, but Fedify does not dereference it. It is not a separate target
for `Create`, `Update`, or `Delete`.

#### Different translators in an organization

Credit each language separately while keeping the organization as the
article's author:

~~~~ typescript twoslash
import { Article, LanguageString, Translation } from "@fedify/vocab";

const id = new URL("https://example.com/articles/2");
const updated = Temporal.Instant.from("2026-09-10T00:00:00Z");
const article = new Article({
id,
attribution: new URL("https://example.com/orgs/acme"),
updated,
contents: [
new LanguageString("Original", "en"),
new LanguageString("日本語版", "ja"),
new LanguageString("한국어판", "ko"),
],
translations: [
new Translation({
language: new Intl.Locale("ja"),
translator: new URL("https://example.com/users/bob"),
original: id,
sourceUpdated: updated,
}),
new Translation({
language: new Intl.Locale("ko"),
translators: [
new URL("https://example.com/actors/translation-service"),
new URL("https://example.com/users/carol"),
],
original: id,
sourceUpdated: updated,
}),
],
});
~~~~

Here the Korean entry credits both an `Application` that produced machine
output and a `Person` who reviewed it. Carol's presence credits review, not
unaided translation. Unreviewed machine output should credit only the
software actor and omit the human-review timestamp.

#### Source edits and review acknowledgements

Compare a translation's `sourceUpdated` with the source's `updated`, or
`published` when `updated` is absent. An earlier value means potentially
stale; an equal or later value reflects the current source. If either the
translation's baseline or the source's reference timestamp is absent, freshness
is unknown.

A source edit leaves the translations' existing baselines intact. A subsequent
human review can advance one baseline without changing its text:

~~~~ typescript twoslash
import { Article, Update } from "@fedify/vocab";
declare const article: Article;
declare const authorizedPublisher: URL;
// ---cut-before---
const edited = article.clone({
updated: Temporal.Instant.from("2026-09-15T00:00:00Z"),
});
// After a human reviews the Korean translation against this source:
const reviewed = edited.clone({
translations: edited.translations.map((translation) =>
translation.language?.baseName === "ko"
? translation.clone({ sourceUpdated: edited.updated })
: translation
),
});
const update = new Update({
id: new URL("https://example.com/activities/review-123"),
actor: authorizedPublisher,
object: reviewed,
});
~~~~

The server-to-server `Update` carries the full current object, including
unchanged language maps and the other translations. To withdraw a language,
remove both its text and its metadata entry in that update. This full-state
replacement rule does not apply to client-to-server partial updates.

#### Reading credit safely

`translation.translatorIds` preserves actor references even when the accounts
are unavailable. `getTranslator()` and `getTranslators()` use the usual
[origin checks](./vocab.md#same-origin-policy-for-properties). A translation's
metadata ID does not establish trust in embedded actors: actors with IDs are
fetched unless the application explicitly trusts them or supplied them locally.

To classify a translation as human, machine, or reviewed, resolve *all*
credited actors. The plural accessor
can skip deleted, unavailable, or refused actors, so do not classify only the
successful results or use the singular accessor for this purpose:

~~~~ typescript twoslash
import { Application, Service, Translation } from "@fedify/vocab";
declare const translation: Translation;
// ---cut-before---
const ids = translation.translatorIds;
const actors = await Array.fromAsync(
translation.getTranslators({ suppressError: true }),
);
let credit: "unknown" | "human" | "machine" | "reviewed" = "unknown";
if (
actors.length > 0 && actors.length >= ids.length &&
actors.every((actor) => actor.id != null)
) {
const machineCount = actors.filter((actor) =>
actor instanceof Application || actor instanceof Service
).length;
credit = machineCount === 0
? "human"
: machineCount === actors.length
? "machine"
: "reviewed";
}
~~~~

Without `suppressError`, a failed lookup may throw; treat that result as
unknown as well. An actor without an ID cannot be dereferenced, so its asserted
type cannot establish the classification.

Translator credit is a claim made by the publishing server. Neither this
credit nor `attributedTo` grants an actor permission to update or delete the
article. Keep your existing publishing authorization and signature checks;
parsing these fields does not verify the credit or authorize an activity.

[FEP-22cd]: https://w3id.org/fep/22cd

### `Question`: Polls

The `Question` type is used for polls. In Mastodon, the question body comes
Expand Down
Loading
Loading