setLanguageGroupFallback method

Future<void> setLanguageGroupFallback({
  1. required String locale,
  2. required String newFallback,
  3. List<String>? validLocales,
})

T023: Sets a language group fallback for a locale. Validates that the fallback is in the same language group, prevents circular references, and checks locale existence. Also enforces FR-010: directionality constraint for fallbacks.

Implementation

Future<void> setLanguageGroupFallback({
  required String locale,
  required String newFallback,
  List<String>? validLocales,
}) async {
  final state = await _stateStore.load(
    config: config,
    projectRootPath: projectRootPath,
  );

  // Check self-reference
  if (locale == newFallback) {
    throw CatalogOperationException(
      'Locale cannot fallback to itself',
    );
  }

  // Check language group constraint
  if (!sameLanguageGroup(locale, newFallback)) {
    throw CatalogOperationException(
      'Fallback must be in same language group',
    );
  }

  // FR-010: Enforce fallback directionality constraint
  // Base language can only fallback to: base language or other base languages
  // Regional variant can fallback to: regional variant or base language
  // NOT allowed: Base → Regional (e.g., en → ar_SA)
  final sourceIsRegional = _isLocaleRegional(locale);
  final targetIsRegional = _isLocaleRegional(newFallback);

  if (!sourceIsRegional && targetIsRegional) {
    throw CatalogOperationException(
      'Invalid fallback direction: base language "$locale" cannot fall back to regional variant "$newFallback". '
      'Only these directions allowed: Regional→Regional, Regional→Base, Base→Base',
    );
  }

  // Check circular fallback
  if (hasCircularFallback(state.languageGroupFallbacks, locale, newFallback)) {
    throw CatalogOperationException(
      'Setting this fallback would create circular reference',
    );
  }

  // Check if locales exist (if validLocales provided)
  if (validLocales != null) {
    if (!validLocales.contains(locale)) {
      throw CatalogOperationException('Locale "$locale" does not exist');
    }
    if (!validLocales.contains(newFallback)) {
      throw CatalogOperationException('Fallback locale "$newFallback" does not exist');
    }
  }

  // Update state
  final updatedFallbacks = Map<String, String>.from(state.languageGroupFallbacks)..[locale] = newFallback;
  final updatedState = state.copyWith(languageGroupFallbacks: updatedFallbacks);

  await _stateStore.save(
    config: config,
    projectRootPath: projectRootPath,
    state: updatedState,
  );
}