<commit_before><?php

namespace App\Models\Services;

use App\FileNotFoundException;
use App\InvalidArgumentException;
use App\InvalidStateException;
use App\Models\Structs\Gender;
use Monolog\Logger;
use Nette;
use Nette\Neon\Neon;


/**
 * Starting key with "/" removes prefix
 * Syntax [male|female] if key GENDER is set
 * Args can be named or numbered
 */
class Translator implements Nette\Localization\ITranslator
{

	const GENDER = 'gender';

	/**
	 * @var string
	 */
	private $language;

	/**
	 * @var string path
	 */
	private $dir;

	/**
	 * @var array
	 */
	private $source;

	/**
	 * @var Logger
	 */
	private $log;

	/**
	 * @var string
	 */
	private $prefix;

	public function __construct($dir, Logger $log)
	{
		$this->dir = $dir;
		$this->log = $log;
	}

	public function setLanguage($language)
	{
		$this->language = $language;

		$this->source = $this->getSource("$this->dir/$language.yml", $language);
	}

	public function setPrefix($prefix)
	{
		$this->prefix = $prefix;
	}

	protected function getSource($file, $language)
	{
		if (!file_exists($file))
		{
			throw new FileNotFoundException("Localization file for '$language' not found at '$file'");
		}
		$raw = file_get_contents($file);
		return Neon::decode($raw);
	}

	/**
	 * @param int $count
	 * @return array of possible keys, ordered by priority
	 */
	private function countToKey($count)
	{
		$count = abs($count);
		if ($count === 0)
		{
			return ['zero', 'many'];
		}
		else if ($count == 1)
		{
			return ['one'];
		}
		else if ($count >= 2 && $count <= 4)
		{
			return ['few', 'many'];
		}
		else
		{
			return ['many'];
		}
	}

	private function fallback($steps, $count)
	{
		if ($count !== NULL)
		{
			$keys = implode('|', $this->countToKey($count));

			return "[$steps ($keys)]";
		}

		return "[$steps]";
	}

	/**
	 * Translates the given string.
	 *
	 * @param string $steps
	 * @param int $count
	 * @param array $args
	 * @throws \App\InvalidStateException
	 * @throws \App\InvalidArgumentException
	 * @return string
	 */
	public function translate($steps, $count = NULL, array $args = [])
	{
		// alias for translate($steps, $args)
		if (is_array($count) && !$args)
		{
			$args = $count;
			$count = NULL;
		}

		if (strpos($steps, ' ') !== FALSE)
		{
			throw new InvalidArgumentException("Translator expects key such as 'homepage.flash.error', not an actual message.");
		}

		if ($this->language === NULL)
		{
			throw new InvalidStateException('Translator language not set');
		}

		if (strpos($steps, '/') === 0)
		{
			$steps = ltrim($steps, '/');
		}
		else
		{
			$steps = $this->prefix . $steps;
		}

		$text = $this->findTextByKey($steps, $count);
		if ($text === NULL)
		{
			return $this->fallback($steps, $count);
		}

		$values = $args;
		$values[1] = $count;
		$translated = preg_replace_callback('~%([\w\d]+)~', function ($match) use ($values, $steps)
		{
			$key = $match[1];
			if (!isset($values[$key]))
			{
				$count = count($values);
				$this->log->addNotice("Translation for '$steps' has placeholder for '$match[0]' but arg count is $count", [$this->language]);
				return $match[0];
			}

			return $values[$key];
		}, $text);

		if (isset($values[self::GENDER]))
		{
			// inflect by gender from '[he|she]' syntax
			$isMale = $values[self::GENDER] === Gender::MALE;
			$translated = preg_replace_callback('~\[([^|]*)\|([^|]*)\]~', function($m) use ($isMale) {
				return $isMale ? $m[1] : $m[2];
			}, $translated);
		}

		return $translated;
	}<commit_msg>add inflection to translator<commit_after><?php

namespace App\Models\Services;

use App\FileNotFoundException;
use App\InvalidArgumentException;
use App\InvalidStateException;
use App\Models\Structs\Gender;
use Inflection;
use Monolog\Logger;
use Nette;
use Nette\Neon\Neon;


/**
 * Starting key with "/" removes prefix
 *
 * Syntax [male|female] if key GENDER is set
 * - [Uložils|Uložilas] formulář
 * - Uložil[|a]s formulář
 *
 * Args can be named or numbered
 *  - Vítej %1
 *  - Vítej %name
 *
 * Args can be inflected to proper case
 *  - Vítek %name(3) # vocative
 */
class Translator implements Nette\Localization\ITranslator
{

	const GENDER = 'gender';

	/**
	 * @var string
	 */
	private $language;

	/**
	 * @var string path
	 */
	private $dir;

	/**
	 * @var array
	 */
	private $source;

	/**
	 * @var Logger
	 */
	private $log;

	/**
	 * @var string
	 */
	private $prefix;

	public function __construct($dir, Logger $log)
	{
		$this->dir = $dir;
		$this->log = $log;
	}

	public function setLanguage($language)
	{
		$this->language = $language;

		$this->source = $this->getSource("$this->dir/$language.yml", $language);
	}

	public function setPrefix($prefix)
	{
		$this->prefix = $prefix;
	}

	protected function getSource($file, $language)
	{
		if (!file_exists($file))
		{
			throw new FileNotFoundException("Localization file for '$language' not found at '$file'");
		}
		$raw = file_get_contents($file);
		return Neon::decode($raw);
	}

	/**
	 * @param int $count
	 * @return array of possible keys, ordered by priority
	 */
	private function countToKey($count)
	{
		$count = abs($count);
		if ($count === 0)
		{
			return ['zero', 'many'];
		}
		else if ($count == 1)
		{
			return ['one'];
		}
		else if ($count >= 2 && $count <= 4)
		{
			return ['few', 'many'];
		}
		else
		{
			return ['many'];
		}
	}

	private function fallback($steps, $count)
	{
		if ($count !== NULL)
		{
			$keys = implode('|', $this->countToKey($count));

			return "[$steps ($keys)]";
		}

		return "[$steps]";
	}

	/**
	 * Translates the given string.
	 *
	 * @param string $steps
	 * @param int $count
	 * @param array $args
	 * @throws \App\InvalidStateException
	 * @throws \App\InvalidArgumentException
	 * @return string
	 */
	public function translate($steps, $count = NULL, array $args = [])
	{
		// alias for translate($steps, $args)
		if (is_array($count) && !$args)
		{
			$args = $count;
			$count = NULL;
		}

		if (strpos($steps, ' ') !== FALSE)
		{
			throw new InvalidArgumentException("Translator expects key such as 'homepage.flash.error', not an actual message.");
		}

		if ($this->language === NULL)
		{
			throw new InvalidStateException('Translator language not set');
		}

		if (strpos($steps, '/') === 0)
		{
			$steps = ltrim($steps, '/');
		}
		else
		{
			$steps = $this->prefix . $steps;
		}

		$text = $this->findTextByKey($steps, $count);
		if ($text === NULL)
		{
			return $this->fallback($steps, $count);
		}

		$values = $args;
		$values[1] = $count;
		$translated = preg_replace_callback('~%([\w\d]+)(?:\((?P<case>[a-z1-7])\))?~i', function ($match) use ($values, $steps)
		{
			$key = $match[1];
			if (!isset($values[$key]))
			{
				$count = count($values);
				$this->log->addNotice("Translation for '$steps' has placeholder for '$match[0]' but arg count is $count", [$this->language]);
				return $match[0];
			}

			if (isset($match['case']))
			{
				return Inflection::inflect($values[$key], [Inflection::CASE_N => $match['case']]);
			}
			return $values[$key];
		}, $text);

		if (isset($values[self::GENDER]))
		{
			// inflect by gender from '[he|she]' syntax
			$isMale = $values[self::GENDER] === Gender::MALE;
			$translated = preg_replace_callback('~\[([^|]*)\|([^|]*)\]~', function($m) use ($isMale) {
				return $isMale ? $m[1] : $m[2];
			}, $translated);
		}

		return $translated;
	}
{'fraction_non_alphanumeric': 0.1898457758973659, 'fraction_numerical': 0.0035485191756516993, 'mean_word_length': 3.372315035799523, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7300370', 'n_tokens_mistral': 2953, 'n_tokens_neox': 2558, 'n_words': 623}