Возникла необходимость установки циркуляционного насоса в полотенцесушителе, поскольку через некоторое время после ремонта горячая вода перестала самотёком заходить в него. Был куплен сам насос и смонтирован куда надо, с физикой всё в порядке. А вот с менеджментом и экономикой стало как-то грустно: даже заявленные 25 Вт мощности насоса выливаются в 219 кВт*ч потребления за год, или, если в деньгах - примерно в 800 рублей добавочных расходов. Уменьшить энергопотребление можно различными путями: 1. Заменить насос на менее производительный и маломощный; 2. запускать насос эпизодически, по мере надобности (остывания полотенцесушителей). С первым пунктом всё сложно: чем ниже энергопотребление, тем выше цены. Второй пункт можно реализовать при помощи механических, электронных таймеров (300-700 рублей) или термореле ( >2000 рублей). Бытовые таймеры страдают либо грубым квантованием времени (шаг 15 минут в суточных механических) или недостаточным количеством программ (порядка 20 в электронных), ну а готовые термореле дороговаты и требуют перенастройки при изменении температуры теплоносителя.

В связи с вышесказанным родилась хотелка самонастраиваемого устройства регулировки работы насоса. Предъявляемые требования: низкое энергопотребление, автоматическая адаптация к температуре теплоносителя, самонастройка длительности времени работы/простоя.

Для реализации использовал МК семейства Arduino (Пробовал как Mega, так и Nano), цифровой термодатчик Dallas DS18xxx, резистор на 4,7кОм, реле рабочим напряжением 5В и коммутируемым 220В (я брал твердотельное OMRON), и блок питания на 5В постоянного тока. Разумеется, нужны ещё провода, пара клеммников и коробка, куда будет уложено всё богатство.  Схема творения не сохранилась, но способы подключения реле и термодатчиков широко описаны в примерах на различных сайтах, ничего нового изобретено не было. Термодатчик был размещён на выходящей из полотенцесушителя трубе, ближе к магистрали.

Принцип работы.

При включении МК включается насос, начинает измеряться температура. Насос работает, пока растёт температура на выходной трубе. При прекращении роста температуры отсчитывается wait_before_pump_off секунд и насос отключается. Далее МК ожидает падения температуры на hysteresis градусов, после чего насос включается и цикл повторяется. Если по какой-то причине (изначально недостаточная температура теплоносителя, хорошая теплоизоляция полотенцесушителя, ошибочное значение параметров) температура не может снизиться на величину гистерезиса, контроллер отсчитывает max_state_time_counter секунд и включает насос. Эти три параметра влияют на соотношение и длительность интервалов вкл/выкл. На практике достигнуто соотношение работы/отдыха на уровне порядка 1/3,5 при данных значениях параметров.

Код скетча:

#include <OneWire.h>

struct TempRelay {
  int PumpAddress;        //Пин выхода на силовое реле
  bool Heating;           //Cостояние реле вкл/выкл
  byte addr[8];           //Адрес датчика Dallas
  byte type_s;            //Тип датичка Dallas
  int time_counter;       //Время работы насоса
  int state_time_counter; //Время нахождения в текущем состоянии
  int higher_temp;        //Максимально достигнутая температура * 16
};

int wait_before_pump_off = 60; // Время работы насоса после достижения максимальной температуры трубы
int hysteresis = 5*16; // Градусы Цельсия, умноженные на 16 (приведение к raw-формату dallas). Падение температуры для повторного включения насоса.
int max_state_time_counter = 3600; //Максимальное время нахождения в одном из состояний, в секундах.
int loop_delay_calculator_counter = 0;
int loop_timer = 0; // Время выполнения одного прохода функции loop. Для вычисления задержки.
int loop_first_time_check = 0;

TempRelay TR;
OneWire  ds(2);  // Работать с датчиками Dallas на пине 2 (нужен резистор на 4.7 кОм)


void setup(void) {
  int i = 0;
  Serial.println("Init...");
  Serial.begin(9600);
  TR = {4, false, {0,0,0,0,0,0,0,0},0, 0, 0, -55*16};
  pinMode(TR.PumpAddress, OUTPUT);
  
  ds.write(0x60);
  int sensors = 0;
  while (ds.search(TR.addr)){
   sensors++;
  };
  ds.reset_search();
  if (!sensors) {
    Serial.println("No DS18xxx sensors.");
  }
  else
  {
  Serial.print("Sensors on the wire: ");
  Serial.println(sensors);
 
  Serial.print("HW id:");
  for( i = 0; i < 8; i++) {
    Serial.write(' ');
    Serial.print(TR.addr[i], HEX);
  } 
    if (OneWire::crc8(TR.addr, 7) != TR.addr[7]) {
      Serial.println("CRC is not valid! Bad wire?");
      return;
  }
  Serial.println(); 
  }
  // the first ROM byte indicates which chip
  switch (TR.addr[0]) {
    case 0x10:
      Serial.println("  Chip = DS18S20");  // or old DS1820
      TR.type_s = 1;
      break;
    case 0x28:
      Serial.println("  Chip = DS18B20");
      TR.type_s = 0;
      break;
    case 0x22:
      Serial.println("  Chip = DS1822");
      TR.type_s = 0;
      break;
    default:
      Serial.println("Device is not a DS18x20 family device.");
      return;
  }
  int16_t raw = read_dallas(&ds, TR.addr);
  delay(1000); // Дать время на формирование результата
  raw = read_dallas(&ds, TR.addr); 
  pump_on(&TR);
  pump_diag(&TR, raw);
  Serial.println("Init complete...");    
}

 
void loop() {
//Засечка времени начала первого прохода
  if (loop_delay_calculator_counter == 0)
    loop_first_time_check = millis();
 
  loop_delay_calculator_counter++;

  int16_t raw = 0;
  raw = read_dallas(&ds, TR.addr);


  TR.state_time_counter++;
  if (TR.Heating) {
    if (TR.higher_temp < raw){
      TR.higher_temp = raw;
      TR.time_counter = 0;
    }
    else
      TR.time_counter++;
    if(TR.time_counter > wait_before_pump_off || TR.state_time_counter > max_state_time_counter){
      pump_diag(&TR, (float)raw);
      pump_off(&TR);
    }
  }
  else
  {
    if (raw + hysteresis < TR.higher_temp || TR.state_time_counter > max_state_time_counter){
      pump_diag(&TR, (float)raw);
      pump_on(&TR);
    }
  }

if (loop_delay_calculator_counter > 100) 
  {
  loop_delay_calculator_counter--;
  delay(1000-loop_timer); //Подгоняем продолжительность цикла к 1 секунде. Приблизительно. Особого практического смысла эта фича не имеет, можно просто поставить задержку в 1 сек.
  }
else
  {
  if (loop_delay_calculator_counter == 100) // Считаем среднее время выполнения одного цикла после прохождения 100 циклов
    loop_timer = (millis() - loop_first_time_check) / 100;
  }
  TR.state_time_counter--;
  TR.time_counter = 0;
}

int16_t read_dallas(OneWire *ds, byte *address){
  byte data[12];
  byte present = 0;
  ds->reset();
  ds->select(address);
// при паразитном питании нужна задержка.
  ds->write(0x44, 1);        // start conversion, with parasite power on at the end
//  delay(1000);     // maybe 750ms is enough, maybe not
  // we might do a ds.depower() here, but the reset will take care of it.
 
  present = ds->reset();
  ds->select(address);   
  ds->write(0xBE);

//  Serial.print("  Data = ");
//  Serial.print(present, HEX);
//  Serial.print(" ");
  for (byte i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds->read();
//    Serial.print(data[i], HEX);
//    Serial.print(" ");
  }

/*  float celsius;
  celsius = (float)raw_temp(TR.type_s, data) / 16.0;
  Serial.print("Temperature = ");
  Serial.print(celsius);
  Serial.println(" Celsius"); 

*/ 
//  Serial.print(" CRC=");
//  Serial.print(OneWire::crc8(data, 8), HEX);
//  Serial.println();
  return raw_temp(TR.type_s, data);
}

void pump_diag(TempRelay *no, float raw){
  float celsius = raw / 16.0;
  Serial.print("Reached temperature = ");
  Serial.print(celsius);
  Serial.println(" Celsius");
 
  Serial.print("Pump was ");
  Serial.print(no->state_time_counter);
  Serial.print(" sec. ");
  Serial.println(no->Heating?"on":"off");
}

void pump_off(TempRelay *no){
 digitalWrite(no->PumpAddress, HIGH); // почему в nano - HIGH, а в mega LOW?
 no->Heating = false;
 no->state_time_counter = 0;
}

void pump_on(TempRelay *no){
 digitalWrite(no->PumpAddress, LOW); // почему в nano - LOW, а в mega HIGH?
 no->Heating = true;
 no->time_counter = 0;
 no->higher_temp = 0;
 no->state_time_counter = 0;
}

int16_t raw_temp(int type_s, byte *data)
{
  // Convert the data to actual temperature
  // because the result is a 16 bit signed integer, it should
  // be stored to an "int16_t" type, which is always 16 bits
  // even when compiled on a 32 bit processor.
  int16_t raw = (data[1] << 8) | data[0];
  if (type_s) {
    raw = raw << 3; // 9 bit resolution default
    if (data[7] == 0x10) {
      // "count remain" gives full 12 bit resolution
      raw = (raw & 0xFFF0) + 12 - data[6];
    }
  } else {
    byte cfg = (data[4] & 0x60);
    // at lower res, the low bits are undefined, so let's zero them
    if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
    else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
    else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
    //// default is 12 bit resolution, 750 ms conversion time
  }
  return raw; 
}

Возникла задача сделать многоязычное приложение на Java с выводом чисел и указанием того, что, собственно, выводится, например: 2 кирпича, 10 лет, 5 подушек и т.д. Известные решения (gettext, ChoiceFormat) меня не воодушевили, так как в одном случае надо порождать кучу файлов (по числу языков) и цеплять объёмную библиотеку, а во втором - некорректно обрабатывается русский язык. Решение было принято в пользу создания своего велосипеда и написании небольшого класса, который осуществляет выбор формы существительного при числительном в зависимости от языка и значения числительного.

Информация для "мяса" класса была взята отсюда: http://translate.sourceforge.net/wiki/l10n/pluralforms. Использовать, правда, её с ходу не получилось, так как она содержит некоторые ошибки в синтаксисе, впрочем, не фатальные.

Использование класса осуществляется следующим образом: при создании объекта вызывается конструктор, который либо сам пытается выяснить текущую локаль, либо ему её указывают принудительно. Далее, в нём производится сопоставление кода локали ISO и внутреннего "кода языка". Собственно говоря, это самая долгая операция, поэтому лучше её делать как можно реже. После этого можно вызывать метод convert(int value, "текст1|текст2|..."); на вход которого подаётся число и несколько форм существительного, которые представляют собой одну строку и разделяются вертикальной чертой.

 

 

// Data about plural forms get here:
// http://translate.sourceforge.net/wiki/l10n/pluralforms

import java.util.Locale;

public class i10n_pluralforms {
	private int lang_id = 0;
	private int nplurals = 0;
	private String language = "";

	public i10n_pluralforms (Locale locale){
		language = locale.getLanguage();
		lang_id = languageToIntId(language);
		return;
	}
	public i10n_pluralforms (){
		Locale locale = Locale.getDefault();
		language = locale.getLanguage();
		lang_id = languageToIntId(language);
		return;
	}

private int languageToIntId(String language)
{
		 if (language.compareToIgnoreCase("ach") == 0){lang_id = 1;}
	else if (language.compareToIgnoreCase("af") == 0){lang_id = 2;}
	else if (language.compareToIgnoreCase("ak") == 0){lang_id = 3;}
	else if (language.compareToIgnoreCase("sq") == 0){lang_id = 4;}
	else if (language.compareToIgnoreCase("am") == 0){lang_id = 5;}
	else if (language.compareToIgnoreCase("ar") == 0){lang_id = 6;}
	else if (language.compareToIgnoreCase("an") == 0){lang_id = 7;}
	else if (language.compareToIgnoreCase("es_AR") == 0){lang_id = 8;}
	else if (language.compareToIgnoreCase("hy") == 0){lang_id = 9;}
	else if (language.compareToIgnoreCase("ast") == 0){lang_id = 10;}
	else if (language.compareToIgnoreCase("ay") == 0){lang_id = 11;}
	else if (language.compareToIgnoreCase("az") == 0){lang_id = 12;}
	else if (language.compareToIgnoreCase("eu") == 0){lang_id = 13;}
	else if (language.compareToIgnoreCase("be") == 0){lang_id = 14;}
	else if (language.compareToIgnoreCase("bn") == 0){lang_id = 15;}
	else if (language.compareToIgnoreCase("bs") == 0){lang_id = 16;}
	else if (language.compareToIgnoreCase("pt_BR") == 0){lang_id = 17;}
	else if (language.compareToIgnoreCase("br") == 0){lang_id = 18;}
	else if (language.compareToIgnoreCase("bg") == 0){lang_id = 19;}
	else if (language.compareToIgnoreCase("ca") == 0){lang_id = 20;}
	else if (language.compareToIgnoreCase("cgg") == 0){lang_id = 21;}
	else if (language.compareToIgnoreCase("zh") == 0){lang_id = 22;}
	else if (language.compareToIgnoreCase("kw") == 0){lang_id = 23;}
	else if (language.compareToIgnoreCase("hr") == 0){lang_id = 24;}
	else if (language.compareToIgnoreCase("cs") == 0){lang_id = 25;}
	else if (language.compareToIgnoreCase("da") == 0){lang_id = 26;}
	else if (language.compareToIgnoreCase("nl") == 0){lang_id = 27;}
	else if (language.compareToIgnoreCase("dz") == 0){lang_id = 28;}
	else if (language.compareToIgnoreCase("en") == 0){lang_id = 29;}
	else if (language.compareToIgnoreCase("eo") == 0){lang_id = 30;}
	else if (language.compareToIgnoreCase("et") == 0){lang_id = 31;}
	else if (language.compareToIgnoreCase("fo") == 0){lang_id = 32;}
	else if (language.compareToIgnoreCase("fil") == 0){lang_id = 33;}
	else if (language.compareToIgnoreCase("fi") == 0){lang_id = 34;}
	else if (language.compareToIgnoreCase("fr") == 0){lang_id = 35;}
	else if (language.compareToIgnoreCase("fy") == 0){lang_id = 36;}
	else if (language.compareToIgnoreCase("fur") == 0){lang_id = 37;}
	else if (language.compareToIgnoreCase("ff") == 0){lang_id = 38;}
	else if (language.compareToIgnoreCase("gl") == 0){lang_id = 39;}
	else if (language.compareToIgnoreCase("ka") == 0){lang_id = 40;}
	else if (language.compareToIgnoreCase("de") == 0){lang_id = 41;}
	else if (language.compareToIgnoreCase("el") == 0){lang_id = 42;}
	else if (language.compareToIgnoreCase("gu") == 0){lang_id = 43;}
	else if (language.compareToIgnoreCase("gun") == 0){lang_id = 44;}
	else if (language.compareToIgnoreCase("ha") == 0){lang_id = 45;}
	else if (language.compareToIgnoreCase("he") == 0){lang_id = 46;}
	else if (language.compareToIgnoreCase("hi") == 0){lang_id = 47;}
	else if (language.compareToIgnoreCase("hu") == 0){lang_id = 48;}
	else if (language.compareToIgnoreCase("is") == 0){lang_id = 49;}
	else if (language.compareToIgnoreCase("id") == 0){lang_id = 50;}
	else if (language.compareToIgnoreCase("ia") == 0){lang_id = 51;}
	else if (language.compareToIgnoreCase("ga") == 0){lang_id = 52;}
	else if (language.compareToIgnoreCase("it") == 0){lang_id = 53;}
	else if (language.compareToIgnoreCase("ja") == 0){lang_id = 54;}
	else if (language.compareToIgnoreCase("jv") == 0){lang_id = 55;}
	else if (language.compareToIgnoreCase("kn") == 0){lang_id = 56;}
	else if (language.compareToIgnoreCase("csb") == 0){lang_id = 57;}
	else if (language.compareToIgnoreCase("kk") == 0){lang_id = 58;}
	else if (language.compareToIgnoreCase("km") == 0){lang_id = 59;}
	else if (language.compareToIgnoreCase("ko") == 0){lang_id = 60;}
	else if (language.compareToIgnoreCase("ku") == 0){lang_id = 61;}
	else if (language.compareToIgnoreCase("ky") == 0){lang_id = 62;}
	else if (language.compareToIgnoreCase("lo") == 0){lang_id = 63;}
	else if (language.compareToIgnoreCase("lv") == 0){lang_id = 64;}
	else if (language.compareToIgnoreCase("lb") == 0){lang_id = 65;}
	else if (language.compareToIgnoreCase("ln") == 0){lang_id = 66;}
	else if (language.compareToIgnoreCase("lt") == 0){lang_id = 67;}
	else if (language.compareToIgnoreCase("jbo") == 0){lang_id = 68;}
	else if (language.compareToIgnoreCase("mk") == 0){lang_id = 69;}
	else if (language.compareToIgnoreCase("mai") == 0){lang_id = 70;}
	else if (language.compareToIgnoreCase("mg") == 0){lang_id = 71;}
	else if (language.compareToIgnoreCase("ms") == 0){lang_id = 72;}
	else if (language.compareToIgnoreCase("ml") == 0){lang_id = 73;}
	else if (language.compareToIgnoreCase("mt") == 0){lang_id = 74;}
	else if (language.compareToIgnoreCase("mnk") == 0){lang_id = 75;}
	else if (language.compareToIgnoreCase("mi") == 0){lang_id = 76;}
	else if (language.compareToIgnoreCase("arn") == 0){lang_id = 77;}
	else if (language.compareToIgnoreCase("mr") == 0){lang_id = 78;}
	else if (language.compareToIgnoreCase("mfe") == 0){lang_id = 79;}
	else if (language.compareToIgnoreCase("mn") == 0){lang_id = 80;}
	else if (language.compareToIgnoreCase("nah") == 0){lang_id = 81;}
	else if (language.compareToIgnoreCase("nap") == 0){lang_id = 82;}
	else if (language.compareToIgnoreCase("ne") == 0){lang_id = 83;}
	else if (language.compareToIgnoreCase("se") == 0){lang_id = 84;}
	else if (language.compareToIgnoreCase("nso") == 0){lang_id = 85;}
	else if (language.compareToIgnoreCase("no") == 0){lang_id = 86;}
	else if (language.compareToIgnoreCase("nb") == 0){lang_id = 87;}
	else if (language.compareToIgnoreCase("nn") == 0){lang_id = 88;}
	else if (language.compareToIgnoreCase("oc") == 0){lang_id = 89;}
	else if (language.compareToIgnoreCase("or") == 0){lang_id = 90;}
	else if (language.compareToIgnoreCase("pap") == 0){lang_id = 91;}
	else if (language.compareToIgnoreCase("ps") == 0){lang_id = 92;}
	else if (language.compareToIgnoreCase("fa") == 0){lang_id = 93;}
	else if (language.compareToIgnoreCase("pms") == 0){lang_id = 94;}
	else if (language.compareToIgnoreCase("pl") == 0){lang_id = 95;}
	else if (language.compareToIgnoreCase("pt") == 0){lang_id = 96;}
	else if (language.compareToIgnoreCase("pa") == 0){lang_id = 97;}
	else if (language.compareToIgnoreCase("ro") == 0){lang_id = 98;}
	else if (language.compareToIgnoreCase("rm") == 0){lang_id = 99;}
	else if (language.compareToIgnoreCase("ru") == 0){lang_id = 100;}
	else if (language.compareToIgnoreCase("sco") == 0){lang_id = 101;}
	else if (language.compareToIgnoreCase("gd") == 0){lang_id = 102;}
	else if (language.compareToIgnoreCase("sr") == 0){lang_id = 103;}
	else if (language.compareToIgnoreCase("si") == 0){lang_id = 104;}
	else if (language.compareToIgnoreCase("sk") == 0){lang_id = 105;}
	else if (language.compareToIgnoreCase("sl") == 0){lang_id = 106;}
	else if (language.compareToIgnoreCase("so") == 0){lang_id = 107;}
	else if (language.compareToIgnoreCase("son") == 0){lang_id = 108;}
	else if (language.compareToIgnoreCase("es") == 0){lang_id = 109;}
	else if (language.compareToIgnoreCase("su") == 0){lang_id = 110;}
	else if (language.compareToIgnoreCase("sw") == 0){lang_id = 111;}
	else if (language.compareToIgnoreCase("sv") == 0){lang_id = 112;}
	else if (language.compareToIgnoreCase("tg") == 0){lang_id = 113;}
	else if (language.compareToIgnoreCase("ta") == 0){lang_id = 114;}
	else if (language.compareToIgnoreCase("tt") == 0){lang_id = 115;}
	else if (language.compareToIgnoreCase("te") == 0){lang_id = 116;}
	else if (language.compareToIgnoreCase("th") == 0){lang_id = 117;}
	else if (language.compareToIgnoreCase("bo") == 0){lang_id = 118;}
	else if (language.compareToIgnoreCase("ti") == 0){lang_id = 119;}
	else if (language.compareToIgnoreCase("tr") == 0){lang_id = 120;}
	else if (language.compareToIgnoreCase("tk") == 0){lang_id = 121;}
	else if (language.compareToIgnoreCase("uk") == 0){lang_id = 122;}
	else if (language.compareToIgnoreCase("ur") == 0){lang_id = 123;}
	else if (language.compareToIgnoreCase("ug") == 0){lang_id = 124;}
	else if (language.compareToIgnoreCase("uz") == 0){lang_id = 125;}
	else if (language.compareToIgnoreCase("vi") == 0){lang_id = 126;}
	else if (language.compareToIgnoreCase("wa") == 0){lang_id = 127;}
	else if (language.compareToIgnoreCase("cy") == 0){lang_id = 128;}
	else if (language.compareToIgnoreCase("wo") == 0){lang_id = 129;}
	else if (language.compareToIgnoreCase("sah") == 0){lang_id = 130;}
	else if (language.compareToIgnoreCase("yo") == 0){lang_id = 131;}
	else lang_id = 0;
	return lang_id;
}

public String convert (int integer, String pluralforms){
	int plural = 0;
	int n = Math.abs(integer);

	switch (lang_id)
	{
	case 1: nplurals=2; plural=(n > 1?1:0); break;
	case 2: nplurals=2; plural=(n == 1?0:1); break;
	case 3: nplurals=2; plural=(n > 1?1:0); break;
	case 4: nplurals=2; plural=(n == 1?0:1); break;
	case 5: nplurals=2; plural=(n > 1?1:0); break;
	case 6: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); break;
	case 7: nplurals=2; plural=(n == 1?0:1); break;
	case 8: nplurals=2; plural=(n == 1?0:1); break;
	case 9: nplurals=2; plural=(n == 1?0:1); break;
	case 10: nplurals=2; plural=(n == 1?0:1); break;
	case 11: nplurals=1; plural=0; break;
	case 12: nplurals=2; plural=(n == 1?0:1); break;
	case 13: nplurals=2; plural=(n == 1?0:1); break;
	case 14: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); break;
	case 15: nplurals=2; plural=(n == 1?0:1); break;
	case 16: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); break;
	case 17: nplurals=2; plural=(n == 1?0:1); break;
	case 18: nplurals=2; plural=(n > 1?1:0); break;
	case 19: nplurals=2; plural=(n == 1?0:1); break;
	case 20: nplurals=2; plural=(n == 1?0:1); break;
	case 21: nplurals=1; plural=0; break;
	case 22: nplurals=1; plural=0; break;
	case 23: nplurals=4; plural= ((n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3); break;
	case 24: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); break;
	case 25: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); break;
	case 26: nplurals=2; plural=(n == 1?0:1); break;
	case 27: nplurals=2; plural=(n == 1?0:1); break;
	case 28: nplurals=1; plural=0; break;
	case 29: nplurals=2; plural=(n == 1?0:1); break;
	case 30: nplurals=2; plural=(n == 1?0:1); break;
	case 31: nplurals=2; plural=(n == 1?0:1); break;
	case 32: nplurals=2; plural=(n == 1?0:1); break;
	case 33: nplurals=2; plural=(n > 1?1:0); break;
	case 34: nplurals=2; plural=(n == 1?0:1); break;
	case 35: nplurals=2; plural=(n > 1?1:0); break;
	case 36: nplurals=2; plural=(n == 1?0:1); break;
	case 37: nplurals=2; plural=(n == 1?0:1); break;
	case 38: nplurals=2; plural=(n == 1?0:1); break;
	case 39: nplurals=2; plural=(n == 1?0:1); break;
	case 40: nplurals=1; plural=0; break;
	case 41: nplurals=2; plural=(n == 1?0:1); break;
	case 42: nplurals=2; plural=(n == 1?0:1); break;
	case 43: nplurals=2; plural=(n == 1?0:1); break;
	case 44: nplurals=2; plural =(n > 1?1:0); break;
	case 45: nplurals=2; plural=(n == 1?0:1); break;
	case 46: nplurals=2; plural=(n == 1?0:1); break;
	case 47: nplurals=2; plural=(n == 1?0:1); break;
	case 48: nplurals=2; plural=(n == 1?0:1); break;
	case 49: nplurals=2; plural=((n%10!=1 || n%100==11)?1:0); break;
	case 50: nplurals=1; plural=0; break;
	case 51: nplurals=2; plural=(n == 1?0:1); break;
	case 52: nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4); break;
	case 53: nplurals=2; plural=(n == 1?0:1); break;
	case 54: nplurals=1; plural=0; break;
	case 55: nplurals=2; plural=(n == 0?0:1); break;
	case 56: nplurals=2; plural=(n == 1?0:1); break;
	case 57: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); break;
	case 58: nplurals=1; plural=0; break;
	case 59: nplurals=1; plural=0; break;
	case 60: nplurals=1; plural=0; break;
	case 61: nplurals=2; plural=(n == 1?0:1); break;
	case 62: nplurals=1; plural=0; break;
	case 63: nplurals=1; plural=0; break;
	case 64: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2); break;
	case 65: nplurals=2; plural=(n == 1?0:1); break;
	case 66: nplurals=2; plural=(n > 1?1:0); break;
	case 67: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); break;
	case 68: nplurals=1; plural=0; break;
	case 69: nplurals=2; plural= (n==1 || n%10==1 ? 0 : 1); break;
	case 70: nplurals=2; plural=(n == 1?0:1); break;
	case 71: nplurals=2; plural=(n > 1?1:0); break;
	case 72: nplurals=1; plural=0; break;
	case 73: nplurals=2; plural=(n == 1?0:1); break;
	case 74: nplurals=4; plural=(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3); break;
	case 75: nplurals=3; plural=(n==0 ? 0 : n==1 ? 1 : 2); break;
	case 76: nplurals=2; plural=(n > 1?1:0); break;
	case 77: nplurals=2; plural=(n > 1?1:0); break;
	case 78: nplurals=2; plural=(n == 1?0:1); break;
	case 79: nplurals=2; plural=(n > 1?1:0); break;
	case 80: nplurals=2; plural=(n == 1?0:1); break;
	case 81: nplurals=2; plural=(n == 1?0:1); break;
	case 82: nplurals=2; plural=(n == 1?0:1); break;
	case 83: nplurals=2; plural=(n == 1?0:1); break;
	case 84: nplurals=2; plural=(n == 1?0:1); break;
	case 85: nplurals=2; plural=(n == 1?0:1); break;
	case 86: nplurals=2; plural=(n == 1?0:1); break;
	case 87: nplurals=2; plural=(n == 1?0:1); break;
	case 88: nplurals=2; plural=(n == 1?0:1); break;
	case 89: nplurals=2; plural=(n > 1?1:0); break;
	case 90: nplurals=2; plural=(n == 1?0:1); break;
	case 91: nplurals=2; plural=(n == 1?0:1); break;
	case 92: nplurals=2; plural=(n == 1?0:1); break;
	case 93: nplurals=1; plural=0; break;
	case 94: nplurals=2; plural=(n == 1?0:1); break;
	case 95: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); break;
	case 96: nplurals=2; plural=(n == 1?0:1); break;
	case 97: nplurals=2; plural=(n == 1?0:1); break;
	case 98: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2); break;
	case 99: nplurals=2; plural=(n == 1?0:1); break;
	case 100: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); break;
	case 101: nplurals=2; plural=(n == 1?0:1); break;
	case 102: nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3; break;
	case 103: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); break;
	case 104: nplurals=2; plural=(n == 1?0:1); break;
	case 105: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; break;
	case 106: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0); break;
	case 107: nplurals=2; plural=(n == 1?0:1); break;
	case 108: nplurals=2; plural=(n == 1?0:1); break;
	case 109: nplurals=2; plural=(n == 1?0:1); break;
	case 110: nplurals=1; plural=0; break;
	case 111: nplurals=2; plural=(n == 1?0:1); break;
	case 112: nplurals=2; plural=(n == 1?0:1); break;
	case 113: nplurals=2; plural=(n > 1?1:0); break;
	case 114: nplurals=2; plural=(n == 1?0:1); break;
	case 115: nplurals=1; plural=0; break;
	case 116: nplurals=2; plural=(n == 1?0:1); break;
	case 117: nplurals=1; plural=0; break;
	case 118: nplurals=1; plural=0; break;
	case 119: nplurals=2; plural=(n > 1?1:0); break;
	case 120: nplurals=2; plural=(n > 1?1:0); break;
	case 121: nplurals=2; plural=(n == 1?0:1); break;
	case 122: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); break;
	case 123: nplurals=2; plural=(n == 1?0:1); break;
	case 124: nplurals=1; plural=0;; break;
	case 125: nplurals=2; plural=(n > 1?1:0); break;
	case 126: nplurals=1; plural=0; break;
	case 127: nplurals=2; plural=(n > 1?1:0); break;
	case 128: nplurals=4; plural= ((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3); break;
	case 129: nplurals=1; plural=0; break;
	case 130: nplurals=1; plural=0; break;
	case 131: nplurals=2; plural=(n == 1?0:1); break;

	}
	if(nplurals == 0) return "i10n_pluralforms: Oops! Unknown language " + language;
	String parsedString[] = pluralforms.split("\\|");
	if(parsedString.length < nplurals) return "i10n_pluralforms: Oops! Not enough plural forms in \"" + pluralforms + "\" for language " + language;

    return parsedString[plural];	
}

}


Joomla 1.5. Как её есть.

При установке столкнулся с проблемами:

1. Нет русифицированного интерфейса.

2. Не печатаются PDF с русскими буквами.

Решение первой проблемы было найдено на joomla.ru, пусть и не совсем безгрешное: скачать.

Вторая проблема решилась скачиванием дистрибутива TCPDF - PDF class for PHP, выдиранием из него шрифта dejavusans и прописыванием его в языковом модуле в качестве шрифта для PDF.

 

Некоторое время назад искал веб-интерфейс к RADIUS-серверу freeradius с поддержкой СУБД PostgreSQL. Поиски оказались не очень результативными, таких продуктов оказалось только два: веб-интерфейс, идущий в комплекте с исходниками freeradius, и daloRADIUS. Веб-интерфейс freeradius не запустился вовсе (ибо PHP3), а вот daloRADIUS с PgSQL работать не то чтобы отказалась, но регулярно выдавала ошибки.

После анализа исходных текстов стало понятно, что daloRADIUS, хоть и спроектирован для работы с многими СУБД, на данном этапе разработки (Версия 0.9-8) заточен под работу с MySQL, так как все SQL-запросы сделаны с учётом синтаксиса этой СУБД.

В результате для собственных нужд я внёс изменения в логику создания SQL-запросов, которая теперь обеспечивает работу как с MySQL, так и PostgreSQL. Внесённые изменения были оформлены в виде патча к версии daloRADIUS 0.9-8 и содержат, помимо изменённых запросов, SQL-скрипт для создания необходимых для работы daloRADIUS таблиц и записей.

Патч: daloradius-0.9-8-pgsql.patch.bz2