Skip to content

feat(translate): Add GlobalLink translation connector - #689

Open
markdaugherty wants to merge 12 commits into
mainfrom
globallink-translation-connector
Open

feat(translate): Add GlobalLink translation connector#689
markdaugherty wants to merge 12 commits into
mainfrom
globallink-translation-connector

Conversation

@markdaugherty

@markdaugherty markdaugherty commented Aug 26, 2026

Copy link
Copy Markdown
Member
  • Add nx/blocks/loc/connectors/globallink/index.js: isConnected/connect/sendAllLanguages/getStatusAll/saveItems/cancelTranslation against GlobalLink's REST API (submission create, source upload, save/autostart, paginated status polling, deliverable download, per-language cancel via targetIds)
  • Requests routed through the DA_TRANSLATE proxy (adobe/da-translate#9) instead of calling GlobalLink directly from the browser; real per-site endpoint sent via x-globallink-origin header
  • Auth via da-etc (Add GlobalLink login support markdaugherty/da-etc#1), using the shared loc/utils/auth.js (same as Trados/Lionbridge) instead of a connector-specific implementation; requests retry once on a 401 via a forced token refresh, plus standard 429/5xx backoff
  • DA_TRANSLATE in nx2/utils/utils.js now resolved via getEnv (matches DA_ETC/DA_FEEDBACK) instead of a hardcoded string; added missing stage entry; fixed local dev port
  • uploadSourceFiles: source documents uploaded as a single zip (extractArchive=true) instead of one call per file; adds fflate as a dependency (nx2/deps/fflate, mirrors existing lit/mdast bundling)
  • toFileName: DA paths flattened to upload file names with underscore-escaping so distinct paths can't collide on the same name
  • Targets matched back to DA urls via a daBasePath -> documentId map captured at upload time, not fuzzy file-name matching
  • listTargets: pages through all of a submission's targets (200/page) instead of only the first page
  • markTargetsDelivered: mark targets delivered after a successful save, so GlobalLink stops re-surfacing them
  • uploadSourceFiles: detect and warn when GlobalLink splits uploads across additional (untracked) submissions due to the per-submission file limit
  • saveItems: wait for GlobalLink to report a language's deliverables ready before downloading individual targets
  • Batch name generated dynamically per submission (title + timestamp, truncated to 64 UTF-8 chars)
  • Add test/loc/connectors/globallink/index.test.js covering all exported functions
  • Fixes found exercising against a live GlobalLink pilot account:
    • customAttributes support for projects with mandatory submission attributes
    • pageSize capped at 200 (API max)
    • waitForSubmissionReady recognizes the PROCESSED status
    • saveAndAutostart checks startedSubmissionIds in the response body instead of trusting HTTP 200
    • lang.translation ??= {} guards added before every mutation (matches Smartling/Trados)

Mark Daugherty added 3 commits August 25, 2026 16:49
Implements isConnected/connect/sendAllLanguages/getStatusAll/saveItems/
cancelTranslation against GlobalLink's REST API (submission create, source
upload, save/autostart, status polling, deliverable download, and per-language
cancel via targetIds), matching the destructured-object contract the loc UI
actually calls (verified against Smartling/Trados) rather than the positional-arg
signatures the connector originally shipped with.

Requests are proxied through DA_TRANSLATE (new /translate/globallink/<org>/<site>
route in da-translate) instead of hitting GlobalLink directly from the browser,
with the real per-site endpoint passed via an x-globallink-origin header that the
proxy validates against an allowlist.

Fixes found by exercising the connector against a live GlobalLink pilot account:
missing customAttributes support (some projects require mandatory submission
attributes), pageSize=500 exceeding the API's 200 max, waitForSubmissionReady not
recognizing the real "PROCESSED" status, saveAndAutostart trusting HTTP 200 when
the body says the submission didn't actually start, and missing
`lang.translation ??= {}` guards that crashed sendAllLanguages/getStatusAll on
languages with no prior translation state.

DA_TRANSLATE is now resolved via getEnv (matching DA_ETC/DA_FEEDBACK) so it can
point at a local wrangler dev server instead of only production.
Adds connectors/globallink/auth.js, mirroring trados/auth.js: getAccessToken
calls da-etc's new /:org/sites/:site/integrations/globallink/login endpoint
(carrying only the browser's own DA session auth) and caches the resulting
access token in localStorage. isConnected/connect now just delegate to
authReady.

Removes all local OAuth building from index.js (setTokenDetails/
getTokenDetails/clearToken/basicAuthHeader/requestToken/refreshAccessToken/
refreshTheToken and the module-level token/tokenPolling state) — every call
site now gets its token via getAccessToken/authHeaders instead. The GlobalLink
OAuth client secret and the user's password no longer transit through the
browser at all; only the (non-secret) API endpoint used for the DA_TRANSLATE
proxy header stays client-side.

Corresponding DA config keys renamed oauthClient/oauthSecret -> clientId/
clientSecret to match da-etc's shared credential-resolution helper.
8787 collides with DA_ADMIN's local port; da-translate's wrangler dev
serves on 8788.
@aem-code-sync

aem-code-sync Bot commented Aug 26, 2026

Copy link
Copy Markdown

Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch.
In case there are problems, just click the checkbox below to rerun the respective action.

  • Re-sync branch
Commits

- batchName threaded through createSubmission/uploadSourceFile instead of
  a hardcoded 'Batch1' constant
- Defaults to DEFAULT_BATCH_NAME, overridable via service.batchName
  (translation.service.<env>.batchName)
Mark Daugherty added 2 commits August 26, 2026 07:26
- generateBatchName(title): title + timestamp, truncated to 64 UTF-8 chars
  (GlobalLink batch-naming limit)
- Removes service.batchName config option entirely
- markTargetsDelivered: mark targets delivered after a successful save, so
  GlobalLink stops re-surfacing them (POST .../targets/delivered)
- uploadSourceFiles: upload all source documents as a single zip
  (extractArchive=true) instead of one call per file; adds fflate as a
  dependency (nx2/deps/fflate, mirrors existing lit/mdast bundling)
- uploadSourceFiles: detect and warn when GlobalLink splits uploads across
  additional (untracked) submissions due to the per-submission file limit
- saveItems: wait for GlobalLink to report a language's deliverables ready
  (deliverableLanguages + downloadId polling, 5s interval) before downloading
  individual targets
@mhaack mhaack changed the title Add GlobalLink translation connector feat(translate): Add GlobalLink translation connector Aug 28, 2026
Comment thread nx/blocks/loc/connectors/globallink/index.js Outdated
Comment thread nx/blocks/loc/connectors/globallink/index.js Outdated
Comment thread nx/blocks/loc/connectors/globallink/index.js Outdated
Comment thread nx/blocks/loc/connectors/globallink/index.js Outdated
@@ -0,0 +1,790 @@
import { Queue } from '../../../../../nx2/public/utils/tree.js';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a big new connector (790 + 92 lines) with no tests at all, the Smartling one right next to it is well covered. can we at least get auth (token cache/expiry/refresh) and the main send/status/error paths under test before merge?

CC: @mhaack

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not ready for review yet, but yes, I am planning to add test coverage (+ moving authentication to either the da-etc or da-translate proxies to obfuscate the credentials) before moving it out of draft status.

* treated as expired by (already adjusted by {@link TOKEN_BUFFER_MS}).
* @returns {void}
*/
function setTokenDetails(org, site, env, accessToken, expires) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the access token goes in localStorage, that's XSS-readable and sticks around. since it's short-lived, would sessionStorage or in-memory be enough here?

Mark Daugherty added 4 commits September 4, 2026 13:57
- listTargets: page through /rest/v0/targets (pageNumber) instead of
  silently truncating to the first 200 results
- toFileName: escape existing underscores before flattening path
  separators, so distinct DA paths can't collide on the same
  GlobalLink upload file name
- sendAllLanguages: persist submissionId onto options.service,
  matching the Smartling connector's convention
- uploadSourceFiles: record a daBasePath -> documentId map from the
  upload response, persisted via sendAllLanguages onto
  options.service.documentIds
- matchUrl: match targets by documentId only; drop clientIdentifier
  (a submission-level field, not per-document) and fuzzy file-name
  matching
- listTargets: page with a 0-based pageNumber; drop the
  targetStatus/targetLanguage query params (unconfirmed for this
  endpoint) in favor of client-side filtering in each caller
- saveItems/cancelTranslation: filter fetched targets by status
  (PROCESSED or DELIVERED) and target language locally
- Add test/loc/connectors/globallink/index.test.js covering
  isConnected/connect/sendAllLanguages/getStatusAll/saveItems/
  cancelTranslation, at parity with the Smartling connector tests,
  including target pagination and documentId-matching regressions
- Delete the connector's own auth.js and switch to
  loc/utils/auth.js (as Trados/Lionbridge already do), fixing a bug
  where the login URL broke when DA_ETC resolves to undefined
- Route every GlobalLink fetch through fetchWithRetry with a 401
  onUnauthorized handler, so a stale/revoked token gets one forced
  refresh-and-retry instead of failing the request outright
…w gate

da-translate now requires an IMS bearer token on Authorization for
/translate/globallink, mirroring the Google connector's daFetch-based auth.
GlobalLink's own credential moves to a new x-globallink-authorization header.
isConnected/connect now also require a valid IMS session before making any
proxy call. The IMS helpers (imsAccessToken/imsAuthHeader) live in the shared
loc/utils/auth.js so other da-etc-backed connectors can reuse them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants