core_key-generator.js

'use strict';

/**
 * @fileoverview Translation key generation from source text.
 * Slugifies text and derives scope from file paths.
 * @module key-generator
 */

const path = require('path');
const { decodeHtmlEntities } = require('./parser-utils');

const MAX_KEY_LENGTH = 50;
const IGNORED_SCOPE_FOLDERS = new Set([
  'components',
  'pages',
  'shared',
  'common',
  'features',
  'dialogs',
  'forms',
  'ui',
  'lib',
]);

/**
 * Converts text to a translation key (lowercase, underscored, max 50 chars)
 * @param {string} text
 * @returns {string}
 * @example
 * slugify("Hello World!") // "hello_world"
 */
function slugify(text) {
  return (
    decodeHtmlEntities(text)
      .toLowerCase()
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '')
      .replace(/'/g, '')
      .replace(/[^a-z0-9\s]/g, ' ')
      .trim()
      .split(/\s+/)
      .filter(w => w.length > 1)
      .slice(0, 6)
      .join('_')
      .substring(0, MAX_KEY_LENGTH) || 'text'
  );
}

/**
 * Derives scope array from file path (e.g., "users/profile.component.ts" → ["users", "profile"])
 * @param {string} filePath
 * @param {string} baseDir
 * @returns {string[]}
 */
function pathToScope(filePath, baseDir) {
  const relative = path.relative(baseDir, filePath);
  const parts = relative.split(path.sep);
  const fileName = parts
    .pop()
    .replace(/\.(component|html|ts)$/g, '')
    .replace(/\.component$/, '');

  const significantParts = parts.filter(p => !IGNORED_SCOPE_FOLDERS.has(p));

  const scope = significantParts
    .concat(fileName !== 'app' && fileName !== parts.at(-1) ? [fileName] : [])
    .filter(Boolean);

  return scope.length > 0 ? scope : ['app'];
}

module.exports = {
  slugify,
  pathToScope,
};