Skip to content
Open
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
99 changes: 99 additions & 0 deletions .github/workflows/update-from-dispatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: update-from-dispatch

on:
repository_dispatch:
types: [api-update]

jobs:
update:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Resolve target branch
id: resolve_branch
run: |
TAG="${{ github.event.client_payload.release_tag }}"
# Fail fast if release_tag is missing or empty
if [[ -z "$TAG" ]]; then
echo "::error::release_tag is missing or empty in the repository_dispatch payload."
exit 1
fi
# Validate that the tag matches the expected semver pattern (e.g. v1.2.3 or 1.2.3)
if [[ ! "$TAG" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "::error::release_tag '$TAG' does not match the expected semver pattern (e.g. v1.2.3 or 1.2.3)."
exit 1
fi
# Strip leading 'v' if present, then extract major.minor
SEMVER="${TAG#v}"
MAJOR=$(echo "$SEMVER" | cut -d. -f1)
MINOR=$(echo "$SEMVER" | cut -d. -f2)
BRANCH="${MAJOR}.${MINOR}.x"
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"

- name: Checkout or create target branch
run: |
BRANCH="${{ steps.resolve_branch.outputs.branch }}"
git fetch origin
if git ls-remote --exit-code --heads origin "$BRANCH"; then
git checkout "$BRANCH"
else
git checkout -b "$BRANCH" origin/master
git push origin "$BRANCH"
fi

- name: Checkout igniteui-angular
uses: actions/checkout@v4
with:
repository: IgniteUI/igniteui-angular
ref: ${{ github.event.client_payload.release_tag }}
path: igniteui-angular

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install dependencies
working-directory: igniteui-angular
run: npm ci

- name: Build igniteui-angular
working-directory: igniteui-angular
run: npm run build:lib

- name: Export TypeDoc JSON
working-directory: igniteui-angular
run: npm run build:typedoc:export

- name: Copy TypeDoc JSON to this repo
run: cp igniteui-angular/dist/igniteui-angular/docs/typescript-exported/igniteui-angular.json typedoc/en/igniteui-angular.json

- name: Export SASS API
working-directory: igniteui-angular
run: npm run build:sassdoc:export

- name: Copy SASS API to this repo
run: |
mkdir -p sassdoc/en
rm -rf sassdoc/en/*
cp -r igniteui-angular/i18nRepo/sassdoc/en/. sassdoc/en/

- name: Extract i18n translatable strings
run: node scripts/i18n-extract.mjs --input=typedoc/en/igniteui-angular.json --output=typedoc/en/i18n-translatable.json

- name: Commit and push changes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
if git diff --cached --quiet; then
echo "No changes to commit; skipping commit and push."
else
git commit -m "chore: update API for ${{ github.event.client_payload.release_tag }} ($(date -u +%Y-%m-%d))"
git push origin HEAD
fi
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ typings/

# next.js build output
.next

# Cloned repository used by CI workflow
igniteui-angular/
153 changes: 153 additions & 0 deletions scripts/i18n-extract.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* i18n-extract.mjs
*
* Walks the typedoc EN JSON and produces a flat id → translation-unit map.
* All comment parts (text + inline code) are merged into a single string per
* field so that translation agents receive plain prose, not a fragmented array.
*
* Output format (single file):
* {
* "totalItems": 1234,
* "items": {
* "27444": { "summary": "Defines the possible positions…" },
* "27500": {
* "summary": "Gets or sets the `value`.",
* "blockTags": [
* { "tag": "@param", "name": "value", "text": "The value to set." },
* { "tag": "@returns", "text": "The resolved value." }
* ]
* }
* }
* }
*
* Usage:
* node i18n-extract.mjs
* node i18n-extract.mjs --input=i18nRepo/typedoc/en/igniteui-angular.json
* node i18n-extract.mjs --output=i18n-translatable.json
* node i18n-extract.mjs --chunk-size=50 (items per chunk file, 0 = single file)
Comment on lines +23 to +27
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

The usage header still references i18nRepo/typedoc/en/..., but this repo stores the TypeDoc JSON under typedoc/en/... at the repository root. Please update the usage examples to match the actual expected paths (and ideally show node scripts/i18n-extract.mjs ... from repo root).

Suggested change
* Usage:
* node i18n-extract.mjs
* node i18n-extract.mjs --input=i18nRepo/typedoc/en/igniteui-angular.json
* node i18n-extract.mjs --output=i18n-translatable.json
* node i18n-extract.mjs --chunk-size=50 (items per chunk file, 0 = single file)
* Usage (from repo root):
* node scripts/i18n-extract.mjs
* node scripts/i18n-extract.mjs --input=typedoc/en/igniteui-angular.json
* node scripts/i18n-extract.mjs --output=typedoc/en/i18n-translatable.json
* node scripts/i18n-extract.mjs --chunk-size=50 (items per chunk file, 0 = single file)

Copilot uses AI. Check for mistakes.
*/

import { readFileSync, writeFileSync, mkdirSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));

// ── CLI args ──────────────────────────────────────────────────────────────────
const args = Object.fromEntries(
process.argv.slice(2)
.filter(a => a.startsWith('--'))
.map(a => { const [k, ...v] = a.slice(2).split('='); return [k, v.join('=')]; })
);

const EN_PATH = args.input ?? resolve(__dirname, './typedoc/en/igniteui-angular.json');
const OUT_PATH = args.output ?? resolve(__dirname, './typedoc/en/i18n-translatable.json');
const CHUNK_SIZE = parseInt(args['chunk-size'] ?? '0', 10); // 0 = no splitting
const CHUNKS_DIR = args['chunks-dir'] ?? resolve(__dirname, 'i18n-chunks');
Comment on lines +43 to +46
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

The default paths are resolved relative to scripts/ (resolve(__dirname, './typedoc/...')), but this repo’s typedoc/ folder lives at the repository root (sibling of scripts/). Running the script with no --input/--output will fail because scripts/typedoc/... doesn’t exist. Update the defaults to resolve to ../typedoc/... (or use process.cwd()-relative paths) so the documented node ... usage works out of the box.

Suggested change
const EN_PATH = args.input ?? resolve(__dirname, './typedoc/en/igniteui-angular.json');
const OUT_PATH = args.output ?? resolve(__dirname, './typedoc/en/i18n-translatable.json');
const CHUNK_SIZE = parseInt(args['chunk-size'] ?? '0', 10); // 0 = no splitting
const CHUNKS_DIR = args['chunks-dir'] ?? resolve(__dirname, 'i18n-chunks');
const EN_PATH = args.input ?? resolve(__dirname, '../typedoc/en/igniteui-angular.json');
const OUT_PATH = args.output ?? resolve(__dirname, '../typedoc/en/i18n-translatable.json');
const CHUNK_SIZE = parseInt(args['chunk-size'] ?? '0', 10); // 0 = no splitting
const CHUNKS_DIR = args['chunks-dir'] ?? resolve(dirname(OUT_PATH), 'i18n-chunks');

Copilot uses AI. Check for mistakes.

// ── Helpers ───────────────────────────────────────────────────────────────────

/**
* Merges all parts of a typedoc content array into a single string.
* Code parts (inline `backtick` snippets) are included verbatim so the
* translator sees them as natural markdown prose.
*/
function mergeContent(parts) {
return (parts ?? []).map(p => p.text ?? '').join('').trim();
}

/** Returns true when a comment has anything worth translating. */
function hasTranslatableContent(comment) {
if (!comment) return false;
const hasSummary = !!mergeContent(comment.summary);
const hasBlockTags = comment.blockTags?.some(tag => !!mergeContent(tag.content));
return hasSummary || hasBlockTags;
}

/**
* Converts a typedoc comment into a flat translation unit:
* { summary?: string, blockTags?: Array<{ tag, name?, text }> }
*/
function toTranslationUnit(comment) {
const unit = {};

const summaryText = mergeContent(comment.summary);
if (summaryText) unit.summary = summaryText;

if (comment.blockTags?.length) {
const tags = comment.blockTags
.map(tag => {
const text = mergeContent(tag.content);
if (!text) return null;
const entry = { tag: tag.tag, text };
if (tag.name) entry.name = tag.name;
return entry;
})
.filter(Boolean);
if (tags.length) unit.blockTags = tags;
}

return unit;
}

/**
* Recursive generator — yields every node with translatable content.
*/
function* walkNodes(node) {
if (hasTranslatableContent(node.comment)) {
yield { id: node.id, unit: toTranslationUnit(node.comment) };
}

for (const sig of node.signatures ?? []) {
if (hasTranslatableContent(sig.comment)) {
yield { id: sig.id, unit: toTranslationUnit(sig.comment) };
}
for (const param of sig.parameters ?? []) {
if (hasTranslatableContent(param.comment)) {
yield { id: param.id, unit: toTranslationUnit(param.comment) };
}
}
}

if (node.getSignature && hasTranslatableContent(node.getSignature.comment)) {
yield { id: node.getSignature.id, unit: toTranslationUnit(node.getSignature.comment) };
}

for (const child of node.children ?? []) {
yield* walkNodes(child);
}
}

// ── Main ──────────────────────────────────────────────────────────────────────

console.log(`Reading ${EN_PATH} …`);
const root = JSON.parse(readFileSync(EN_PATH, 'utf8'));

// Flat id → unit map (preserves insertion order = tree order)
const items = {};
for (const topLevel of root.children ?? []) {
for (const { id, unit } of walkNodes(topLevel)) {
items[id] = unit;
}
}

const totalItems = Object.keys(items).length;

if (CHUNK_SIZE > 0) {
mkdirSync(CHUNKS_DIR, { recursive: true });
const entries = Object.entries(items);
let chunkIndex = 0;

for (let i = 0; i < entries.length; i += CHUNK_SIZE) {
const slice = Object.fromEntries(entries.slice(i, i + CHUNK_SIZE));
const filePath = resolve(CHUNKS_DIR, `chunk-${String(chunkIndex).padStart(4, '0')}.json`);
writeFileSync(filePath, JSON.stringify({ items: slice }, null, '\t'), 'utf8');
console.log(` → chunk-${chunkIndex} (${Object.keys(slice).length} items)`);
chunkIndex++;
}

console.log(`\nExtracted ${totalItems} items → ${chunkIndex} chunk files in ${CHUNKS_DIR}/`);
} else {
writeFileSync(OUT_PATH, JSON.stringify({ totalItems, items }, null, '\t'), 'utf8');
console.log(`\nExtracted ${totalItems} items → ${OUT_PATH}`);
}
Loading
Loading