Brave New World

    Прямо сейчас удаленка становится темой месяца. Даже те работодатели, которые очень любят свой офис и считают его своим “домом” вынуждены с этим считаться. В блогах появляются тысячи статей на тему “как готовить удаленку”, технологические компании выпускают свои гайды на эту тему. Этот пост не об этом (свою статью на тему как готовить удаленку я написал, еще в 2017 году, к сожалению, те для кого она была написана ее не читали). Этот пост о том, как я вижу, что ждет нас дальше.
    По-началу всех, конечно, будет штормить, но потом процессы компаний (тут я имею ввиду ИТ-компании) придут в норму и результат труда команд будет такой же, как и когда они были в опенспейсах, а в отдельных случаях даже лучше.
    Но самое интересное будет, когда весь этот катаклизм закончится:
  • Сколько людей после этого захотят вернуться в опенспейсы и насколько это будет РЕАЛЬНО НЕОБХОДИМО?
  • Как рынок труда отреагирует на это?
Лично мой прогноз: удаленка останется с нами навсегда, в одних коллективах как основное направление организации труда, в других как одна из опций к вакансиям (как когда-то стало ДМС). В любом случае на удаленщиков больше не будут смотреть как на чудаков, хотящих странного. Нас ждет удивительный новый мир, который уже не будет таким как раньше.

Удаленная работа и как правильно ее готовить

UPD: Эта статья была написана до того, как ко мне попали такие книги как “Remote” Джейсона Фрайда и “Офис в стиле фанк. Манифест удаленной работы” (Why work sucks and how to fix it) Джоди Томпсон и Кали Ресслер, которые заставили меня немного переосмыслить мои представления о предмете, чего и всем рекомендую.
Речь пойдет об организации удаленки на постоянном месте работы, не о фрилансе.
Ни для кого не секрет, что работодатели очень любят, чтобы их сотрудники находились в офисе и очень не любят, когда те работают удаленно. Происходит это от недоверия: не видно человека – значит непонятно, что он делает. В моей практике мне приходилось слышать от руководства такое: “Откуда нам знать, что ты работаешь? Может ты в приставку играешь?”
Чтобы таких вопросов не возникало и все были довольны я придерживаюсь следующих принципов:
  1. Начинать и заканчивать рабочий день тогда же когда и в офисе
    Если рабочий день в офисе начинается в 9:00, то и работая удаленно в 9:00 надо быть в онлайне. Из этого могут быть исключения, т.к. удаленная работа предоставляет определенную гибкость в планировании своего времени, но тогда надо помнить что 8 часов в день в любом случае принадлежат вашей работе
  2. Время отклика – 10 минут
    Это означает, что если коллеги, например через мессенджер, задают вопрос, их вопрос не проваливается в пустоту и они получают от вас отклик. Это не значит, что надо бросить ту задачу которая в текущий момент выполняется, достаточно ответить “я сейчас работаю над такой-то задачей, через Х минут отвечу на ваш вопрос”. 10 минут вполне достаточно, чтобы зафиксировать свою текущую мысль и ответить. Таким образом люди видят, что вы работаете и вы на связи. Вас не должны “искать”. Если вам нужно куда-то отлучиться, просто поставьте коллег в известность, через какой период времени вы будете на связи.
  3. Всегда надо быть готовым отчитаться о ходе работы
    Тут все очень просто: у вас спрашивают, чем вы сейчас заняты, вы отвечаете, что конкретно вы делаете, например “Я по такой-то задаче делаю то-то”. Тут главное сообщать не просто задачу, а какое  действие по ней вы сейчас совершаете.
  4. Если возникают проблемы в выполнении задачи, сразу сообщать о  них
    Тут все очевидно. Непонятна задача? Нехватает данных? Надо сразу “бить в колокола” а не дожидаться когда вас спросят почему задача не выполнена. Это относится к работе вообще, а не только к удаленке.
Кому-то может показаться, что перечисленные мной правила в какой-то степени ограничивают свободу, присущую удаленке, но тут следует помнить, что удаленная работа – такая же работа как и в офисе, разница только в том, что вы не тратите 2+ часа в день на дорогу и можете сами организовывать свое рабочее место. Эти правила являются компромиссным вариантом, другим вариантом может быть, например, использование в организации приложений слежения/учета времени удаленных сотрудников о которых я возможно расскажу позже.

Вызов хранимой PL/SQL-функции из ADF

Наконец-то разобрался как вызывать хранимые PL/SQL процедуры/функции из ADF, и для тех кто тоже хочет разобраться, но еще этого не сделал (а также для себя, чтобы не забыть), я написал это пошаговое руководство. За основу приложения взят пример из учебника “Developing Rich Web Applications With Oracle ADF” для стандартной демо-схемы БД Oracle HR.

1) В БД добавляем простую функцию (я решил подсчитать сумму зарплаты по департаменту):

create or replace function get_sum_salary_by_dept 
(
  p_dept_id in number 
) return number as 
  v_result number;
begin
  select sum(salary)
    into v_result
    from employees
    where department_id = p_dept_id;
  return v_result;
end get_sum_salary_by_dept;

2) Создаем имплементацию Application Module:
2

3) В получившийся java-класс добавляем общий метод (для удобства я его сделал статическим) запуска хранимых функций:

    // Some constants
        public static int NUMBER = Types.NUMERIC;
        public static int DATE = Types.DATE;
        public static int VARCHAR2 = Types.VARCHAR;

    //Делаем метод статическим и добавляем передачу в него транзакции
    /* В import надо добавить
       import java.sql.CallableStatement;
       import java.sql.SQLException;
       import java.sql.Types;
       import oracle.jbo.JboException;
       import oracle.jbo.server.DBTransaction;
    */
    public static Object callStoredFunction(DBTransaction tr, int sqlReturnType, String stmt,
                                            Object[] bindVars) {
      CallableStatement st = null;
      try {
        // 1. Create a JDBC CallabledStatement  
        st = tr.createCallableStatement(
               "begin ? := "+stmt+";end;",0);
        // 2. Register the first bind variable for the return value
        st.registerOutParameter(1, sqlReturnType);
        if (bindVars != null) {
          // 3. Loop over values for the bind variables passed in, if any
          for (int z = 0; z < bindVars.length; z++) {
            // 4. Set the value of user-supplied bind vars in the stmt
            st.setObject(z + 2, bindVars[z]);
          }
        }
        // 5. Set the value of user-supplied bind vars in the stmt
        st.executeUpdate();
        // 6. Return the value of the first bind variable
        return st.getObject(1);
      }
      catch (SQLException e) {
        throw new JboException(e);
      }
      finally {
         if (st != null) {
            try {
              // 7. Close the statement
              st.close();
            }
            catch (SQLException e) {
               throw new JboException(e);
            }
         }
      }
   }

4) Генерируем класс для объекта сущности:

4

5) В получившийся класс добавляем метод:

public Number callGetSumSalaryByDept() {
      Number n = this.getDepartmentId();
      return (Number)AppModuleImpl.callStoredFunction(getDBTransaction(),
               AppModuleImpl.NUMBER, “get_sum_salary_by_dept(?)”, new Object[]{n});
}

 

6) Генерируем классы для объекта представления и строки представления:

6

7) В класс объекта представления (в моем примере DepartmentsViewImpl.java) добавляем метод, вызывающий метод класса сущности:

    public Float getDepartmentSalarySum(Row row){
        DepartmentsViewRowImpl currentRow = (DepartmentsViewRowImpl)row;
        DepartmentsImpl departmentEntity = currentRow.getDepartments();

        Float res = departmentEntity.callGetSumSalaryByDept().floatValue();
        return res;
    }

 

8) В свойствах представления, на вкладке Java, в разделе Client Interface, кликаем кнопку редактирования и, в открывшемся окне, переносим наш метод в Selected:

8

9) С моделью на этом все. Нажимаем Save All и Rebuid All и переходим к интерфейсу

10) Для управления я взял основную страницу приложения (в моем примере DeptEmpPage.jsf). В AppModuleDataControl присутствует наш метод:

11

11) Из палитры компонентов бросаем на страницу кнопку (Button):

11_

12) У отмеченной кнопки нажимаем на выпадающее меню (Button Actions, справа от кнопки) и выбираем Bind to ADF Control в котором выбираем наш метод:

12

После этого откроется окно Edit Action Binding:

12-2

В этом окне, в параметрах, в колонке Value, нужно открыть выпадающий список и выбрать Show El Expression Builder. В открывшемся окне выбрать источник откуда параметр получит строку (в моем примере текущая строка):
12-3

Нажимаем OK везде, где оно есть

13) В свойствах кнопки, в поле Text переименовываем ее в что-либо более человечное Подмигивающая рожица

14) Из Data Control кидаем возвращаемое значение метода на страницу и выбираем Text –> ADF Output Text w/ Label (можем сразу и изменить label):
14

15) Теперь создадим Partial trigger, чтобы после вызова функции, в компоненте Output Text отобразилось возвращаемое значение. Для этого в свойствах текста (Output Text), в разделе Behavior, нажимаем правую кнопку на свойстве Partial Triggers и выбираем Edit. В открывшемся окне переносим, созданную ранее кнопку в Selected:
15
Нажимаем OK

16) Сохраняем и запускаем наше приложение. После запуска нажимаем кнопку и наслаждаемся результатом:
16

Ссылки:
Мой готовый пример
руководство Developing Rich Web Applications With Oracle ADF
раздел документации Developing Fusion Web Applications with Oracle Application Development Framework – 16 Extending Business Components Functionality

Создание внешних ключей

Из найденого в черновиках. Данный скрипт применялся для докручивания БД, где внешнии ключи отсутствовали как класс
и связи между таблицами строились на основе наименования полей.

declare
  vSQL varchar2(2000);
  vi number;
begin
  vi := 1;
for c in (
select a.table_name, a.column_name, b.table_name pk_table
  from user_tab_columns a,
       user_constraints b,
       user_ind_columns c
  where (a.column_name like 'KD_%' or (a.column_name like 'ID_%' and a.column_name!='ID_USR'))
    and not exists (select 1 from user_constraints b1, user_ind_columns c1
                      where b1.table_name=a.table_name and b1.constraint_type='P' and c1.index_name=b1.index_name
                        and c1.column_name=a.column_name)
    and b.constraint_type='P' and b.table_name!=a.table_name and b.index_name=c.index_name
    and c.column_name = a.column_name) loop
    begin
      vSQL:='create index '||c.table_name||'_i'||to_char(vi)||' on '||c.table_name||' ('||c.column_name||')';
      execute immediate vSQL;
      vi:=vi+1;
    exception
      when others then
        null;
    end;
    begin
      vSQL:='alter table '||c.table_name||' add constraint '||c.table_name||'_fk'||to_char(vi)||' foreign key ('||c.column_name||') '||
            'references '||c.pk_table||' ('||c.column_name||')';
      execute immediate vSQL;
      vi:=vi+1;
    exception
      when others then
        dbms_output.put_line(c.table_name||c.column_name);
        dbms_output.put_line(vSQL);
    end;
end loop;
end;

Типовая задача с собеседований

На которой я почему-то залипаю (при том что знаю решение):

Есть таблица:

create table t
  (id number,
   field_name varchar2(20),
   string_value varchar2(20),
   number_value number);
  
insert into t (id, field_name, string_value, number_value)
  values (1, ‘Name’, ‘John’, null);
insert into t (id, field_name, string_value, number_value)
  values (1, ‘Surname’, ‘Smith’, null); 
insert into t (id, field_name, string_value, number_value)
  values (1, ‘Salary’, null, 100); 
 
insert into t (id, field_name, string_value, number_value)
  values (2, ‘Name’, ‘Bill’, null);
insert into t (id, field_name, string_value, number_value)
  values (2, ‘Surname’, ‘Dow’, null); 
insert into t (id, field_name, string_value, number_value)
  values (2, ‘Salary’, null, 200);   
commit;

надо ее “развернуть” на 90 градусов.

решение простое:

select t.id,
       max(decode(field_name, ‘Name’, string_value)) Name,
       max(decode(field_name, ‘Surname’, string_value)) Surname,
       max(decode(field_name, ‘Salary’, number_value)) Salary
  from t
  group by t.id;

Отладка приложения из OC4J в IDEA

Если у вас приложения для OC4J сделано в JDeveloper 10g то проблем нет – JDeveloper сам понимает контейнер и можно отлаживаться. Но что делать, если вам достается проект, собиреемый Maven’ом и содержащий в себе весь “зоопарк” Java-технологий (Spring, Hibernate и т.д.). Конфигурация для OC4J в IDEA по-умолчанию отсутствует. Поиск статей по этой проблеме на русском языке результатов не дал. Поэтому напишу свою. Все действия происходят под Windows.

Шаг 1. Настраиваем локальный OC4J контейнер

В файл oc4j.cmd вставляем:

set OC4J_JVM_ARGS=-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n -Xms512m -Xmx700m -XX:MaxPermSize=500m
set JVMARGS=%OC4J_JVM_ARGS%

где address=8000 – адрес порта для отладки (это не тот порт к которому идет подключение клиентом или браузером), а -Xms512m -Xmx700m -XX:MaxPermSize=500m – настройки памяти JVM (что они обозначают не имею ни малейшего представления, потому что не настоящий java-программер)

Запускаем: oc4j –start

По-умолчанию порт выставлен как 8888, заходим в Server Control: http;//localhost:8888/em и деплоим предварительно собраный ear-файл.

Шаг 2. Настраиваем IDEA

Открываем проект (если проект сделан в Maven и не создан проект для IDEA, то сначала запускаем mvn idea:idea, а потом открываем получившийся проект)

В IDEA выбираем Run –> Edit configuration. В открывшимся окне нажимаем кнопку “Add new configuration”, в списке выбираем Remote, обзываем по человечески и меняем номер порта на тот который задан в шаге 1 у контейнера (8000)

image

Сохраняем.

Шаг 3. Собственно отладка

Расставляем брек-пойнты, жмем Run –> Debug ‘OC4J’. Подключаемся к контейнеру клиентом, выполняем операции, которые остановятся на брек-пойнте и будет предоставлена отладочная информация.

Вот в общем-то и все.

Отключение триггеров и констрейнтов

Делал это для подгрузки данных в Oracle из дампов. Может кому пригодится.

Сначала создаем 2 таблицы:

create table tmp_dbobj 
  (name varchar2(50), 
   otype varchar2(50));
   
create table tmp_cons_log 
  (cname varchar2(50), 
   ctype varchar2(1), 
   tname varchar2(50), 
   err_text varchar2(2000), 
   cols varchar2(1000), 
   vals varchar2(1000));

Отключаем:

--Выключение констрейнтов
declare
  vDDL varchar2(2000);
begin
  --triggers
  for c_trg in (select * from user_triggers where status = 'ENABLED') loop
    vDDL := 'alter trigger '||c_trg.trigger_name||' disable';
    execute immediate vDDL;
    insert into tmp_dbobj (name, otype)
      values (c_trg.trigger_name, 'trigger');
    commit;
  end loop;
  --FK
  for c_cons in (select * from user_constraints 
                 where constraint_type in ('R') and status = 'ENABLED'
                   --and constraint_name not like 'SYS%'
                 ) loop
    vDDL := 'alter table '||c_cons.table_name||' disable constraint '||c_cons.constraint_name;
    execute immediate vDDL;
    insert into tmp_dbobj (name, otype)
      values (c_cons.constraint_name, 'constraint');
    commit;
  end loop;
  --UK
  for c_cons in (select * from user_constraints 
                 where constraint_type in ('U') and status = 'ENABLED' 
                 ) loop
    vDDL := 'alter table '||c_cons.table_name||' disable constraint '||c_cons.constraint_name;
    execute immediate vDDL;
    insert into tmp_dbobj (name, otype)
      values (c_cons.constraint_name, 'constraint');
    commit;
  end loop;
end;

Данные об отключенных объектах сохраняются в таблицу tmp_dbobj.

Включаем, при этом, если констрейнт не включается происходит проверка, результаты которой (записи на которых констрейнт падает) сохраняются в tmp_cons_log (ВНИМАНИЕ! Тут используется функция – строковый агрегатор, отсутствующая в Oracle):

--проверка и включение констрейнтов
declare
  vSQL varchar2(1000);
  vDDL varchar2(2000);
  vCount number;
begin
  --Уникальные констрайнты
  for c1 in (select a.* 
               from tmp_dbobj a,
                    user_constraints b
               where a.otype = 'constraint'
                 and b.constraint_name = a.name 
                 and b.constraint_type = 'U') loop
    for c_cols in (select constraint_name, table_name, str_agg(column_name) cols, str_agg(column_name||' is null') null_cols
                     from user_cons_columns 
                     where constraint_name = c1.name
                     group by constraint_name, table_name) loop
      c_cols.null_cols := replace(c_cols.null_cols,',',' and ');
      vSQL := 'select 1 from dual where not exists (select '||c_cols.cols||', count(*) cnt from '||
        c_cols.table_name||
        ' where not ('||c_cols.null_cols||') group by '||c_cols.cols||' having count(*)>1 )';
      begin
        execute immediate vSQL into vCount;
        vDDL := 'alter table '||c_cols.table_name||' enable constraint '||c1.name;
        execute immediate vDDL;
        delete from tmp_dbobj where name = c1.name;
        commit;
      exception
        when no_data_found then
          vSQL := 'begin '||
                  'for c1 in ( select '||replace(c_cols.cols,',','||'',''||')||' vals from ('||
                  'select '||c_cols.cols||', count(*) cnt from '||c_cols.table_name||
                  'where not ('||c_cols.null_cols||') group by '||c_cols.cols||' having count(*)>1 )) loop'||
                  'insert into tmp_cons_log (cname, ctype, tname, err_text, cols, vals)'||
                  'values ('''||c_cols.constraint_name||''', ''U'', '''||c_cols.table_name||''', ''Дублирующие значения'', '''||
                  c_cols.cols||''', c1.vals); '||
                  'end loop; commit; end;';
          execute immediate vSQL;
      end;
    end loop;
  end loop;
  --Все остальные (FK)
  for c1 in (select a.*, b.table_name
               from tmp_dbobj a,
                    user_constraints b
               where a.otype = 'constraint'
                 and b.constraint_name = a.name 
                 and b.constraint_type = 'R') loop
    begin
      vDDL := 'alter table '||c1.table_name||' enable constraint '||c1.name;
      execute immediate vDDL;
      delete from tmp_dbobj where name = c1.name;
      commit;
    exception
      when others then
        for c_cols in (select col.constraint_name, col.table_name, r_col.table_name r_table_name,
                              fdc_str_agg(col.column_name) cols, 
                              fdc_str_agg('r.'||r_col.column_name||' = a.'||col.column_name) w_cols,
                              fdc_str_agg('a.'||col.column_name||' is not null') w_cols2
                         from user_constraints con,
                              user_cons_columns col,
                              user_cons_columns r_col
                         where con.constraint_name = c1.name
                           and col.constraint_name = con.constraint_name
                           and r_col.constraint_name = con.r_constraint_name
                           and col.position = r_col.position
                         group by col.constraint_name, col.table_name, r_col.table_name) loop
          vSQL := 'begin '||
                  'for c2 in ( select '||replace(c_cols.cols,',','||'',''||')||' vals from '||
                  c_cols.table_name||' a '||
                  'where not exists (select 1 from '||c_cols.r_table_name||' r '||
                  'where '||replace(c_cols.w_cols,',',' and ')||')'||
                  'and '||replace(c_cols.w_cols2,',',' and ')||
                  ') loop '||
                  'insert into tmp_cons_log (cname, ctype, tname, err_text, cols, vals)'||
                  'values ('''||c_cols.constraint_name||''', ''R'', '''||c_cols.table_name||''', ''Нет родительской записи'', '''||
                  c_cols.cols||''', c2.vals); '||
                  'end loop; commit; end;';
          dbms_output.put_line(vSQL);
          execute immediate vSQL;
        end loop;
    end;
  end loop;
  --триггера
  for c1 in (select * from tmp_dbobj where otype = 'trigger') loop
    vDDL := 'alter trigger '||c1.name||' enable';
    execute immediate vDDL;
    delete from tmp_dbobj where name = c1.name;
    commit;
  end loop;  
end;

Динамический SQL в Oracle. Пример

В рамках программы восстановления утраченных знаний, написал пример использования dbms_sql:

DECLARE
  cSQL NUMBER; –Идентификатор курсора
  vSubjOld NUMBER := 000000; –Старый ID
  vSubjNew NUMBER := 111111; –Новый ID
  vSQL VARCHAR2(1000);
  vID NUMBER;
  vUpdateSQL VARCHAR2(1000);
  vRet NUMBER;
BEGIN
  –Курсор по таблицам с полями которые нужно обработать
  FOR c_tab IN (SELECT DISTINCT tb.TABLE_NAME, cl.COLUMN_NAME
                  FROM user_tab_columns cl,
                       user_tables tb
                  WHERE cl.column_name IN (‘SUBJECT_ID’,’SUBJ_ID’,’DECLARANT_ID’,’PAYER_ID’,’CARRIER_ID’,’BROKER_ID’)
                    AND tb.TABLE_NAME=cl.TABLE_NAME
                    AND tb.TABLE_NAME NOT LIKE ‘EXP_%’
                    AND tb.TABLE_NAME NOT LIKE ‘LEG_%’
                    AND tb.table_name NOT LIKE ‘TMP_%’) LOOP
    dbms_output.put_line(c_tab.table_name||’.’||c_tab.column_name);
    vUpdateSQL := ‘update ‘||c_tab.table_name||’ ‘||
                  ‘set ‘||c_tab.column_name||’ = :1 ‘||
                  ‘where ‘||c_tab.column_name||’ = :2 ‘;
    –открыть курсор
    cSQL:=dbms_sql.open_cursor;
    BEGIN
      vSQL:=’select id ‘||
            ‘from ‘||c_tab.table_name||’ ‘||
            ‘where ‘||c_tab.column_name||’ = :subj_id’;
      dbms_sql.parse(cSQL, vSQL, dbms_sql.native);
      –биндим переменные
      dbms_sql.bind_variable(cSQL, ‘subj_id’, vSubjOld);
      –Определяем колонки
      dbms_sql.define_column(cSQL,1,vID);
      –запускаем
      vRet := dbms_sql.execute(cSQL);
      LOOP
        –В Oracle 11g есть чудесная функция dbms_sql.to_refcursor позволяющая сделать все проще без всех этих условий
        IF dbms_sql.fetch_rows(cSQL)>0 THEN
          dbms_sql.column_value(cSQL,1,vID);
          dbms_output.put_line(‘ID: ‘||vID);
          –Меняем
          EXECUTE IMMEDIATE vUpdateSQL USING vSubjNew, vSubjOld;
        ELSE
          EXIT;
        END IF;
      END LOOP;
      dbms_sql.close_cursor(cSQL);
    EXCEPTION
      WHEN OTHERS THEN
        dbms_output.put_line(SQLERRM);
        dbms_sql.close_cursor(cSQL);
    END;     
  END LOOP;
  –…
END;

Установка русской MSDN Library на английскую VS2010, продолжение

К сожелению выяснилось, что после установки библиотеки, описанной в предыдущей статье, не работает обновление через интернет, а так же добавление новых (например, доку по Silverlight’у) и удаление (!) старых разделов. Обидно, но студию пришлось удалить полностью, потом руками удалить HelpLibrary и потом студию поставить обратно и накатить SP1. LanguagePack на Help не трогал, видимо он остался жить в системе какой-то своей жизнью. Дальше процесс такой:

1. Правим ярлык “Manage Help Settings” как написано в предыдущей статье

2. Устанавливаем русские библиотеки из интернета

3. Правим queryManifest.?.xml: копи-пастим весь каталог с русскими библиотеками и в одной копии меняем атрибут productLocale на “EN-US”

4. Запускаем студию, проверяем. Все работает.

Снимок

Установка русской MSDN Library на английскую Visual Studio 2010

Вот лично у меня локализованные среды разработки вызывают резкую антипатию (ставил ради интереса русскую Visual Studio 2008 долго искал где там Debug Подмигивающая рожица), чего нельзя сказать о документации (хотя она тоже бывает переведена так, что лучше бы и не переводили). Если с 2008 студией все было просто – ставим английскую студию без MSDN потом ставим MSDN от русской студии, то с 2010 студией не все так просто, но не невозможно.

Для установки понадобится: установленная Visual Studio 2010 ENG без библиотек Help’а, дистрибутив Visual Studio 2010 RUS (можно скачать trial, устанавливать ее мы все равно не собираемся), notepad Подмигивающая рожица

1. Распаковываем iso с русской VS2010, вытаскиваем оттуда папки Help и ProductDocumentation

2. Из папки Help устанавливаем Language Pack (HelpSetupLP_x64_RUS.exe или HelpSetupLP_x86_RUS.exe в зависимости от системы)

3. В папке ProductDocumentation правим файл HelpContentSetup.msha заменяя значения ru-ru на en-us

4. Правим ярлык “Microsoft Visual Studio 2010\Visual Studio Tools\Manage Help Settings – ENU” в поле “Объект” меняем параметр /locale с en-US на ru-RU

image

5. Запускаем Manage Help Settings (должно запуститься на русском языке) и выбираем “Установить содержимое с диска”

6. В качестве “Расположения носителя справки” выбираем правленный HelpContentSetup.msha

image

7. Выбираем что хотим установить и давим “Обновить”

image

8. Залезаем в папку с установленным хелпом (по-умолчанию c:\ProgramData\Microsoft\HelpLibrary) и  бекапим её

9. Заходим в catalogs\VS\100 там 2 папки: EN-US и RU-RU, переносим все из EN-US в RU-RU

10. Заходим в manifest и правим файл queryManifest.3.xml методом удаления строк как показано на картинке ниже

image

11. Запускаем студию, вызываем Help

image

12. Убеждаемся что все работает, убиваем бэкапы

PS: Вполне может быть, что какой-то шаг я пропустил, потому, что папка с заготовленной ProductDocumentation у меня была подготовленна задолго до написания этой статьи.

PS2: Вполне возможно, что шаг 9 является лишним

UPD: Продолжение