Index: Makefile.classic ================================================================== --- Makefile.classic +++ Makefile.classic @@ -36,11 +36,11 @@ #TCC = gcc -O6 #TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage TCC = gcc -g -Os -Wall # To add support for HTTPS -TCC += -DFOSSIL_ENABLE_SSL +#TCC += -DFOSSIL_ENABLE_SSL #### Extra arguments for linking the finished binary. Fossil needs # to link against the Z-Lib compression library. There are no # other dependencies. We sometimes add the -static option here # so that we can build a static executable that will run in a @@ -47,11 +47,11 @@ # chroot jail. # LIB = -lz $(LDFLAGS) # If using HTTPS: -LIB += -lcrypto -lssl +#LIB += -lcrypto -lssl #### Tcl shell for use in running the fossil testsuite. If you do not # care about testing the end result, this can be blank. # TCLSH = tclsh Index: src/attach.c ================================================================== --- src/attach.c +++ src/attach.c @@ -49,19 +49,37 @@ " FROM attachment", -1 ); if( zPage ){ if( g.perm.RdWiki==0 ) login_needed(); - style_header("Attachments To %h", zPage); + style_header( +#ifdef LANG_RU + "Файлы к %h", +#elif LANG_EN + "Attachments To %h", +#endif + zPage); blob_appendf(&sql, " WHERE target=%Q", zPage); }else if( zTkt ){ if( g.perm.RdTkt==0 ) login_needed(); - style_header("Attachments To Ticket %.10s", zTkt); + style_header( +#ifdef LANG_RU + "Файлы к задаче %.10s", +#elif LANG_EN + "Attachments To Ticket %.10s", +#endif + zTkt); blob_appendf(&sql, " WHERE target GLOB '%q*'", zTkt); }else{ if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ) login_needed(); - style_header("All Attachments"); + style_header( +#ifdef LANG_RU + "Все файлы" +#elif LANG_EN + "All Attachments" +#endif + ); } blob_appendf(&sql, " ORDER BY mtime DESC"); db_prepare(&q, "%s", blob_str(&sql)); @ <ol> while( db_step(&q)==SQLITE_ROW ){ @@ -91,36 +109,72 @@ @ Attachment %z(href("%R/ainfo/%s",zUuid))%S(zUuid)</a> if( moderation_pending(attachid) ){ @ <span class="modpending">*** Awaiting Moderator Approval ***</span> } @ <br><a href="/attachview?%s(zUrlTail)">%h(zFilename)</a> - @ [<a href="/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br /> + @ [<a href="/attachdownload/%t(zFilename)?%s(zUrlTail)"> +#ifdef LANG_RU + @ скачать +#elif LANG_EN + @ download +#endif + @ </a>]<br /> if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++; if( zComment && zComment[0] ){ @ %w(zComment)<br /> } if( zPage==0 && zTkt==0 ){ if( zSrc==0 || zSrc[0]==0 ){ +#ifdef LANG_RU + zSrc = "Удалён из"; +#elif LANG_EN zSrc = "Deleted from"; +#endif }else { +#ifdef LANG_RU + zSrc = "Добавлен к"; +#elif LANG_EN zSrc = "Added to"; +#endif } if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){ - @ %s(zSrc) ticket <a href="%s(g.zTop)/tktview?name=%s(zTarget)"> +#ifdef LANG_RU + @ %s(zSrc) задач%s(zSrc==0 || zSrc[0]==0 ? "и" : "е") +#elif LANG_EN + @ %s(zSrc) ticket +#endif + @ <a href="%s(g.zTop)/tktview?name=%s(zTarget)"> @ %S(zTarget)</a> }else{ - @ %s(zSrc) wiki page <a href="%s(g.zTop)/wiki?name=%t(zTarget)"> +#ifdef LANG_RU + @ %s(zSrc) страниц%s(zSrc==0 || zSrc[0]==0 ? "ы" : "е") +#elif LANG_EN + @ %s(zSrc) wiki page +#endif + @ <a href="%s(g.zTop)/wiki?name=%t(zTarget)"> @ %h(zTarget)</a> } }else{ if( zSrc==0 || zSrc[0]==0 ){ +#ifdef LANG_RU + @ Удалён +#elif LANG_EN @ Deleted +#endif }else { +#ifdef LANG_RU + @ Добавлен +#elif LANG_EN @ Added +#endif } } +#ifdef LANG_RU + @ пользователем %h(zDispUser) +#elif LANG_EN @ by %h(zDispUser) on +#endif hyperlink_to_date(zDate, "."); free(zUrlTail); } db_finalize(&q); @ </ol> @@ -173,17 +227,27 @@ " ORDER BY mtime DESC LIMIT 1", zTarget, zFile ); } if( zUUID==0 || zUUID[0]==0 ){ +#ifdef LANG_RU + style_header("Нет файлов"); + @ Нет файлов.... +#elif LANG_EN style_header("No Such Attachment"); @ No such attachment.... +#endif style_footer(); return; }else if( zUUID[0]=='x' ){ +#ifdef LANG_RU + style_header("Отсутствует"); + @ Файл был удален +#elif LANG_EN style_header("Missing"); @ Attachment has been deleted +#endif style_footer(); return; } g.perm.Read = 1; cgi_replace_parameter("name",zUUID); @@ -237,11 +301,12 @@ const char *zName = PD("f:filename","unknown"); const char *zTarget; const char *zTargetType; int szContent = atoi(PD("f:bytes","0")); int goodCaptcha = 1; - + const char *zComment = PD("comment", ""); + if( P("cancel") ) cgi_redirect(zFrom); if( zPage && zTkt ) fossil_redirect_home(); if( zPage==0 && zTkt==0 ) fossil_redirect_home(); login_check_credentials(); if( zPage ){ @@ -248,21 +313,33 @@ if( g.perm.ApndWiki==0 || g.perm.Attach==0 ) login_needed(); if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){ fossil_redirect_home(); } zTarget = zPage; - zTargetType = mprintf("Wiki Page <a href=\"%s/wiki?name=%h\">%h</a>", - g.zTop, zPage, zPage); + zTargetType = mprintf( +#ifdef LANG_RU + "странице" +#elif LANG_EN + "Wiki Page" +#endif + " <a href=\"%s/wiki?name=%h\">%h</a>", + g.zTop, zPage, zPage); }else{ if( g.perm.ApndTkt==0 || g.perm.Attach==0 ) login_needed(); if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){ zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag" " WHERE tagname GLOB 'tkt-%q*'", zTkt); if( zTkt==0 ) fossil_redirect_home(); } zTarget = zTkt; - zTargetType = mprintf("Ticket <a href=\"%s/tktview/%S\">%S</a>", + zTargetType = mprintf( +#ifdef LANG_RU + "задаче" +#elif LANG_EN + "Ticket" +#endif + " <a href=\"%s/tktview/%S\">%S</a>", g.zTop, zTkt, zTkt); } if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop); if( P("cancel") ){ cgi_redirect(zFrom); @@ -270,11 +347,10 @@ if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct()) ){ Blob content; Blob manifest; Blob cksum; char *zUUID; - const char *zComment; char *zDate; int rid; int i, n; int addCompress = 0; Manifest *pManifest; @@ -300,11 +376,10 @@ } zName += n; if( zName[0]==0 ) zName = "unknown"; blob_appendf(&manifest, "A %F%s %F %s\n", zName, addCompress ? ".gz" : "", zTarget, zUUID); - zComment = PD("comment", ""); while( fossil_isspace(zComment[0]) ) zComment++; n = strlen(zComment); while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; } if( n>0 ){ blob_appendf(&manifest, "C %F\n", zComment); @@ -317,29 +392,55 @@ attach_put(&manifest, rid, needModerator); assert( blob_is_reset(&manifest) ); db_end_transaction(0); cgi_redirect(zFrom); } +#ifdef LANG_RU + style_header("Добавить файл"); +#elif LANG_EN style_header("Add Attachment"); +#endif if( !goodCaptcha ){ +#ifdef LANG_RU + @ <p class="generalError">Ошибка: неверный код.</p> +#elif LANG_EN @ <p class="generalError">Error: Incorrect security code.</p> +#endif } +#ifdef LANG_RU + @ <h2>Добавление файла к %s(zTargetType)</h2> +#elif LANG_EN @ <h2>Add Attachment To %s(zTargetType)</h2> +#endif form_begin("enctype='multipart/form-data'", "%R/attachadd"); @ <div> + +#ifdef LANG_RU + @ Файл для добавления: +#elif LANG_EN @ File to Attach: - @ <input type="file" name="f" size="60" /><br /> +#endif + @ <input type="file" name="f" size="120" /><br /> +#ifdef LANG_RU + @ Описание:<br /> +#elif LANG_EN @ Description:<br /> - @ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br /> +#endif + @ <textarea name="comment" cols="80" rows="5" wrap="virtual">%h(zComment)</textarea><br /> if( zTkt ){ @ <input type="hidden" name="tkt" value="%h(zTkt)" /> }else{ @ <input type="hidden" name="page" value="%h(zPage)" /> } @ <input type="hidden" name="from" value="%h(zFrom)" /> +#ifdef LANG_RU + @ <input type="submit" name="ok" value="Добавить файл" /> + @ <input type="submit" name="cancel" value="Отмена" /> +#elif LANG_EN @ <input type="submit" name="ok" value="Add Attachment" /> @ <input type="submit" name="cancel" value="Cancel" /> +#endif @ </div> captcha_generate(); @ </form> style_footer(); } @@ -433,19 +534,28 @@ md5sum_blob(&manifest, &cksum); blob_appendf(&manifest, "Z %b\n", &cksum); rid = content_put(&manifest); manifest_crosslink(rid, &manifest); db_end_transaction(0); +#ifdef LANG_RU + @ <p>Указанный файл будет удален.</p> +#elif LANG_EN @ <p>The attachment below has been deleted.</p> +#endif } if( P("del") && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki)) ){ form_begin(0, "%R/ainfo/%s", zUuid); +#ifdef LANG_RU + @ <p>Подтвердите удаление указанного файла. + @ <input type="submit" name="confirm" value="Подтверждаю"> +#elif LANG_EN @ <p>Confirm you want to delete the attachment shown below. @ <input type="submit" name="confirm" value="Confirm"> +#endif @ </form> } isModerator = (zTktUuid && g.perm.ModTkt) || (zWikiName && g.perm.ModWiki); if( isModerator && (zModAction = P("modaction"))!=0 ){ @@ -460,64 +570,121 @@ } if( strcmp(zModAction,"approve")==0 ){ moderation_approve(rid); } } +#ifdef LANG_RU + style_header("Информация о вложении"); + style_submenu_element("Как есть", "Как есть", "%R/artifact/%S", zUuid); + + @ <div class="section">Обзор</div> + @ <p><table class="label-value"> + @ <tr><th>ID артефакта:</th> +#elif LANG_EN style_header("Attachment Details"); style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid); @ <div class="section">Overview</div> @ <p><table class="label-value"> @ <tr><th>Artifact ID:</th> +#endif @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a> if( g.perm.Setup ){ @ (%d(rid)) } modPending = moderation_pending(rid); if( modPending ){ +#ifdef LANG_RU + @ <span class="modpending">*** Ожидает утверждения модератором ***</span> +#elif LANG_EN @ <span class="modpending">*** Awaiting Moderator Approval ***</span> +#endif } if( zTktUuid ){ +#ifdef LANG_RU + @ <tr><th>Задача:</th> +#elif LANG_EN @ <tr><th>Ticket:</th> +#endif @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr> } if( zWikiName ){ +#ifdef LANG_RU + @ <tr><th>Вики-страница:</th> +#elif LANG_EN @ <tr><th>Wiki Page:</th> +#endif @ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr> } +#ifdef LANG_RU + @ <tr><th>Дата:</th><td> +#elif LANG_EN @ <tr><th>Date:</th><td> +#endif hyperlink_to_date(zDate, "</td></tr>"); free(zDate); +#ifdef LANG_RU + @ <tr><th>Пользователь:</th><td> +#elif LANG_EN @ <tr><th>User:</th><td> +#endif hyperlink_to_user(pAttach->zUser, zDate, "</td></tr>"); +#ifdef LANG_RU + @ <tr><th>Артефакт :</th> +#elif LANG_EN @ <tr><th>Artifact Attached:</th> +#endif @ <td>%z(href("%R/artifact/%s",zSrc))%s(zSrc)</a> if( g.perm.Setup ){ @ (%d(ridSrc)) } +#ifdef LANG_RU + @ <tr><th>Имя файла:</th><td>%h(zName)</td></tr> +#elif LANG_EN @ <tr><th>Filename:</th><td>%h(zName)</td></tr> +#endif zMime = mimetype_from_name(zName); if( g.perm.Setup ){ @ <tr><th>MIME-Type:</th><td>%h(zMime)</td></tr> } +#ifdef LANG_RU + @ <tr><th valign="top">Описание:</th><td valign="top">%h(zDesc)</td></tr> +#elif LANG_EN @ <tr><th valign="top">Description:</th><td valign="top">%h(zDesc)</td></tr> +#endif @ </table> if( isModerator && modPending ){ +#ifdef LANG_RU + @ <div class="section">Модерация</div> +#elif LANG_EN @ <div class="section">Moderation</div> +#endif @ <blockquote> form_begin(0, "%R/ainfo/%s", zUuid); @ <label><input type="radio" name="modaction" value="delete"> +#ifdef LANG_RU + @ Удалить это изменение</label><br /> +#elif LANG_EN @ Delete this change</label><br /> +#endif @ <label><input type="radio" name="modaction" value="approve"> +#ifdef LANG_RU + @ Утвердить это изменение</label><br /> +#elif LANG_EN @ Approve this change</label><br /> +#endif @ <input type="submit" value="Submit"> @ </form> @ </blockquote> } +#ifdef LANG_RU + @ <div class="section">Содержимое</div> +#elif LANG_EN @ <div class="section">Content Appended</div> +#endif @ <blockquote> blob_zero(&attach); if( zMime==0 || strncmp(zMime,"text/", 5)==0 ){ const char *z; const char *zLn = P("ln"); @@ -531,11 +698,11 @@ @ %h(z) @ </pre> } }else if( strncmp(zMime, "image/", 6)==0 ){ @ <img src="%R/raw/%S(zSrc)?m=%s(zMime)"></img> - style_submenu_element("Image", "Image", "%R/raw/%S?m=%s", zSrc, zMime); + style_submenu_element("Изображение", "Изображение", "%R/raw/%S?m=%s", zSrc, zMime); }else{ int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc); @ <i>(file is %d(sz) bytes of binary data)</i> } @ </blockquote> @@ -572,16 +739,24 @@ @ %s(zHeader) } cnt++; @ <li> @ %z(href("%R/artifact/%s",zSrc))%h(zFile)</a> +#ifdef LANG_RU + @ добавлено %h(zDispUser) +#elif LANG_EN @ added by %h(zDispUser) on +#endif hyperlink_to_date(zDate, "."); +#ifdef LANG_RU + @ [%z(href("%R/ainfo/%s",zUuid))детали</a>] +#elif LANG_EN @ [%z(href("%R/ainfo/%s",zUuid))details</a>] +#endif @ </li> } if( cnt ){ @ </ul> } db_finalize(&q); } Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -1242,10 +1242,12 @@ }else if( fossil_strcmp(zFieldName,"referer:")==0 ){ cgi_setenv("HTTP_REFERER", zVal); #endif }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ cgi_setenv("HTTP_USER_AGENT", zVal); + }else if( fossil_strcmp(zFieldName,"x-real-ip:")==0 ){ + cgi_replace_parameter("REMOTE_ADDR", mprintf("%s",zVal)); } } cgi_init(); cgi_trace(0); } Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -652,11 +652,11 @@ ){ #ifndef FOSSIL_ALLOW_OUT_OF_ORDER_DATES int b; b = db_exists( "SELECT 1 FROM event" - " WHERE datetime(mtime)>=%Q" + " WHERE strftime('%%Y-%%m-%%dT%%H:%%M:%%f', mtime)>=strftime('%%Y-%%m-%%dT%%H:%%M:%%f', %Q)" " AND type='ci' AND objid=%d", zDate, rid ); if( b ){ fossil_fatal("ancestor check-in [%.10s] (%s) is not older (clock skew?)" Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -45,10 +45,11 @@ /* ** These error messages are shared in multiple locations. They are defined ** here for consistency. */ + #define DIFF_CANNOT_COMPUTE_BINARY \ "cannot compute difference between binary files\n" #define DIFF_CANNOT_COMPUTE_SYMLINK \ "cannot compute difference between symlink and regular file\n" @@ -709,11 +710,11 @@ } } } if( c=='\t' ){ z[j++] = ' '; - while( (k&7)!=7 && k<w ){ z[j++] = ' '; k++; } + while( (k&3)!=3 && k<w ){ z[j++] = ' '; k++; } }else if( c=='\r' || c=='\f' ){ z[j++] = ' '; }else if( c=='<' && p->escHtml ){ memcpy(&z[j], "<", 4); j += 4; Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -85,11 +85,23 @@ db_prepare(&q, "SELECT uuid, pid, isprim FROM plink JOIN blob ON pid=rid " " WHERE cid=%d" " ORDER BY isprim DESC, mtime DESC /*sort*/", rid); while( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); - const char *zType = db_column_int(&q, 2) ? "parent:" : "merged-from:"; + const char *zType = db_column_int(&q, 2) ? +#ifdef LANG_RU + "родитель:" +#elif LANG_EN + "parent:" +#endif + : +#ifdef LANG_RU + "слито с:" +#elif LANG_EN + "merged-from:" +#endif + ; zDate = db_text("", "SELECT datetime(mtime) || ' UTC' FROM event WHERE objid=%d", db_column_int(&q, 1) ); fossil_print("%-13s %s %s\n", zType, zUuid, zDate); @@ -99,11 +111,23 @@ db_prepare(&q, "SELECT uuid, cid, isprim FROM plink JOIN blob ON cid=rid " " WHERE pid=%d" " ORDER BY isprim DESC, mtime DESC /*sort*/", rid); while( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); - const char *zType = db_column_int(&q, 2) ? "child:" : "merged-into:"; + const char *zType = db_column_int(&q, 2) ? +#ifdef LANG_RU + "потомок:" +#elif LANG_EN + "child:" +#endif + : +#ifdef LANG_RU + "слито в:" +#elif LANG_EN + "merged-into:" +#endif + ; zDate = db_text("", "SELECT datetime(mtime) || ' UTC' FROM event WHERE objid=%d", db_column_int(&q, 1) ); fossil_print("%-13s %s %s\n", zType, zUuid, zDate); @@ -111,15 +135,27 @@ } db_finalize(&q); } zTags = info_tags_of_checkin(rid, 0); if( zTags && zTags[0] ){ - fossil_print("tags: %s\n", zTags); + fossil_print( +#ifdef LANG_RU + "теги: %s\n", +#elif LANG_EN + "tags: %s\n", +#endif + zTags); } free(zTags); if( zComment ){ - fossil_print("comment: "); + fossil_print( +#ifdef LANG_RU + "комментарий: " +#elif LANG_EN + "comment: " +#endif + ); comment_print(zComment, 14, 79); free(zComment); } } @@ -461,12 +497,22 @@ login_check_credentials(); if( !g.perm.Read ){ login_needed(); return; } zName = P("name"); rid = name_to_rid_www("name"); if( rid==0 ){ - style_header("Check-in Information Error"); + style_header( +#ifdef LANG_RU + "Ошибка информации о фиксации" +#elif LANG_EN + "Check-in Information Error" +#endif + ); +#ifdef LANG_RU + @ Объект не найден: %h(g.argv[2]) +#elif LANG_EN @ No such object: %h(g.argv[2]) +#endif style_footer(); return; } zRe = P("regex"); if( zRe ) re_compile(&pRe, zRe, 0); @@ -486,11 +532,17 @@ rid, rid ); sideBySide = atoi(PD("sbs","1")); if( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); - char *zTitle = mprintf("Check-in [%.10s]", zUuid); + char *zTitle = mprintf( +#ifdef LANG_RU + "Фиксация [%.10s]", +#elif LANG_EN + "Check-in [%.10s]", +#endif + zUuid); char *zEUser, *zEComment; const char *zUser; const char *zComment; const char *zDate; const char *zOrigDate; @@ -506,37 +558,80 @@ TAG_COMMENT, rid); zUser = db_column_text(&q, 2); zComment = db_column_text(&q, 3); zDate = db_column_text(&q,1); zOrigDate = db_column_text(&q, 4); +#ifdef LANG_RU + @ <div class="section">Обзор</div> +#elif LANG_EN @ <div class="section">Overview</div> +#endif @ <table class="label-value"> - @ <tr><th>SHA1 Hash:</th><td>%s(zUuid) + @ <tr><th> +#ifdef LANG_RU + @ SHA1 хэш: +#elif LANG_EN + @ SHA1 Hash: +#endif + @ </th><td>%s(zUuid) if( g.perm.Setup ){ +#ifdef LANG_RU + @ (ID записи: %d(rid)) +#elif LANG_EN @ (Record ID: %d(rid)) +#endif } @ </td></tr> - @ <tr><th>Date:</th><td> +#ifdef LANG_RU + @ <tr><th>Дата:</th><td> +#elif LANG_EN + @ <tr><th>Date:</th><td> +#endif hyperlink_to_date(zDate, "</td></tr>"); if( zOrigDate && fossil_strcmp(zDate, zOrigDate)!=0 ){ +#ifdef LANG_RU + @ <tr><th>Оригинальная дата:</th><td> +#elif LANG_EN @ <tr><th>Original Date:</th><td> +#endif hyperlink_to_date(zOrigDate, "</td></tr>"); } if( zEUser ){ +#ifdef LANG_RU + @ <tr><th>Исправлявший пользователь:</th><td> +#elif LANG_EN @ <tr><th>Edited User:</th><td> +#endif hyperlink_to_user(zEUser,zDate,"</td></tr>"); +#ifdef LANG_RU + @ <tr><th>Первый пользователь:</th><td> +#elif LANG_EN @ <tr><th>Original User:</th><td> +#endif hyperlink_to_user(zUser,zDate,"</td></tr>"); }else{ +#ifdef LANG_RU + @ <tr><th>Пользователь:</th><td> +#elif LANG_EN @ <tr><th>User:</th><td> +#endif hyperlink_to_user(zUser,zDate,"</td></tr>"); } if( zEComment ){ +#ifdef LANG_RU + @ <tr><th>Исправленный комментарий:</th><td>%w(zEComment)</td></tr> + @ <tr><th>Первоначальный комментарий:</th><td>%w(zComment)</td></tr> +#elif LANG_EN @ <tr><th>Edited Comment:</th><td>%w(zEComment)</td></tr> @ <tr><th>Original Comment:</th><td>%w(zComment)</td></tr> +#endif }else{ +#ifdef LANG_RU + @ <tr><th>Comment:</th><td>%w(zComment)</td></tr> +#elif LANG_EN @ <tr><th>Comment:</th><td>%w(zComment)</td></tr> +#endif } if( g.perm.Admin ){ db_prepare(&q, "SELECT rcvfrom.ipaddr, user.login, datetime(rcvfrom.mtime)" " FROM blob JOIN rcvfrom USING(rcvid) LEFT JOIN user USING(uid)" @@ -546,17 +641,35 @@ if( db_step(&q)==SQLITE_ROW ){ const char *zIpAddr = db_column_text(&q, 0); const char *zUser = db_column_text(&q, 1); const char *zDate = db_column_text(&q, 2); if( zUser==0 || zUser[0]==0 ) zUser = "unknown"; +#ifdef LANG_RU + @ <tr><th>Получено от:</th> + @ <td>%h(zUser) @ %h(zIpAddr) %s(zDate)</td></tr> +#elif LANG_EN @ <tr><th>Received From:</th> @ <td>%h(zUser) @ %h(zIpAddr) on %s(zDate)</td></tr> +#endif } db_finalize(&q); } if( g.perm.Hyperlink ){ const char *zProjName = db_get("project-name", "unnamed"); +#ifdef LANG_RU + @ <tr><th>События:</th><td> + @ %z(href("%R/timeline?f=%S",zUuid))ближние</a> + if( zParent ){ + @ | %z(href("%R/timeline?p=%S",zUuid))родители</a> + } + if( !isLeaf ){ + @ | %z(href("%R/timeline?d=%S",zUuid))потомки</a> + } + if( zParent && !isLeaf ){ + @ | %z(href("%R/timeline?dp=%S",zUuid))обои</a> + } +#elif LANG_EN @ <tr><th>Timelines:</th><td> @ %z(href("%R/timeline?f=%S",zUuid))family</a> if( zParent ){ @ | %z(href("%R/timeline?p=%S",zUuid))ancestors</a> } @@ -564,10 +677,11 @@ @ | %z(href("%R/timeline?d=%S",zUuid))descendants</a> } if( zParent && !isLeaf ){ @ | %z(href("%R/timeline?dp=%S",zUuid))both</a> } +#endif db_prepare(&q, "SELECT substr(tag.tagname,5) FROM tagxref, tag " " WHERE rid=%d AND tagtype>0 " " AND tag.tagid=tagxref.tagid " " AND +tag.tagname GLOB 'sym-*'", rid); while( db_step(&q)==SQLITE_ROW ){ @@ -578,78 +692,151 @@ /* The Download: line */ if( g.perm.Zip ){ char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s", - zProjName, zUuid, zUuid); + zProjName, zUuid, zUuid); @ </td></tr> @ <tr><th>Downloads:</th><td> @ %z(href("%s",zUrl))Tarball</a> @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zProjName,zUuid,zUuid)) +#ifdef LANG_RU + @ ZIP архив</a> +#elif LANG_EN @ ZIP archive</a> +#endif fossil_free(zUrl); } - @ </td></tr> + @ </td></tr> +#ifdef LANG_RU + @ <tr><th>Другие ссылки:</th> + @ <td> + @ %z(href("%R/dir?ci=%S",zUuid))файлы</a> + @ | %z(href("%R/artifact/%S",zUuid))манифест</a> +#elif LANG_EN @ <tr><th>Other Links:</th> @ <td> @ %z(href("%R/dir?ci=%S",zUuid))files</a> @ | %z(href("%R/fileage?name=%S",zUuid))file ages</a> @ | %z(href("%R/artifact/%S",zUuid))manifest</a> +#endif if( g.perm.Write ){ +#ifdef LANG_RU + @ | %z(href("%R/ci_edit?r=%S",zUuid))изменить</a> +#elif LANG_EN @ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a> +#endif } @ </td> @ </tr> } @ </table> }else{ - style_header("Check-in Information"); + style_header( +#ifdef LANG_RU + "Информация о фиксации" +#elif LANG_EN + "Check-in Information" +#endif + ); login_anonymous_available(); } db_finalize(&q); showTags(rid, ""); if( zParent ){ +#ifdef LANG_RU + @ <div class="section">Изменения</div> +#elif LANG_EN @ <div class="section">Changes</div> +#endif @ <div class="sectionmenu"> showDiff = g.zPath[0]!='c'; if( db_get_boolean("show-version-diffs", 0)==0 ){ showDiff = !showDiff; if( showDiff ){ @ %z(xhref("class='button'","%R/vinfo/%T",zName)) +#ifdef LANG_RU + @ спрятать различия</a> +#elif LANG_EN @ hide diffs</a> +#endif if( sideBySide ){ @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName)) +#ifdef LANG_RU + @ различия разом</a> +#elif LANG_EN @ unified diffs</a> +#endif }else{ @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName)) +#ifdef LANG_RU + @ различия бок-о-бок</a> +#elif LANG_EN @ side-by-side diffs</a> +#endif } }else{ @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName)) +#ifdef LANG_RU + @ показать различия разом</a> +#elif LANG_EN @ show unified diffs</a> +#endif @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName)) +#ifdef LANG_RU + @ показать различия бок-о-бок</a> +#elif LANG_EN @ show side-by-side diffs</a> +#endif } }else{ if( showDiff ){ - @ %z(xhref("class='button'","%R/ci/%T",zName))hide diffs</a> + @ %z(xhref("class='button'","%R/ci/%T",zName)) +#ifdef LANG_RU + @ спрятать различия</a> +#elif LANG_EN + @ hide diffs</a> +#endif if( sideBySide ){ @ %z(xhref("class='button'","%R/info/%T?sbs=0",zName)) +#ifdef LANG_RU + @ различия разом</a> +#elif LANG_EN @ unified diffs</a> +#endif }else{ @ %z(xhref("class='button'","%R/info/%T?sbs=1",zName)) +#ifdef LANG_RU + @ различия бок-о-бок</a> +#elif LANG_EN @ side-by-side diffs</a> +#endif } }else{ @ %z(xhref("class='button'","%R/vinfo/%T?sbs=0",zName)) +#ifdef LANG_RU + @ показать различия разом</a> +#elif LANG_EN @ show unified diffs</a> +#endif @ %z(xhref("class='button'","%R/vinfo/%T?sbs=1",zName)) +#ifdef LANG_RU + @ показать различия бок-о-бок</a> +#elif LANG_EN @ show side-by-side diffs</a> +#endif } } + @ @ %z(xhref("class='button'","%R/vpatch?from=%S&to=%S",zParent,zUuid)) - @ patch</a></div> +#ifdef LANG_RU + @ патч</a> +#elif LANG_EN + @ patch</a> +#endif + @</div> + if( pRe ){ @ <p><b>Only differences that match regular expression "%h(zRe)" @ are shown.</b></p> } db_prepare(&q, @@ -696,12 +883,22 @@ login_check_credentials(); if( !g.perm.RdWiki ){ login_needed(); return; } rid = name_to_rid_www("name"); if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI))==0 ){ - style_header("Wiki Page Information Error"); + style_header( +#ifdef LANG_RU + "Ошибка информации о странице" +#elif LANG_EN + "Wiki Page Information Error" +#endif + ); +#ifdef LANG_RU + @ Объект не найден: %h(g.argv[2]) +#elif LANG_EN @ No such object: %h(P("name")) +#endif style_footer(); return; } if( g.perm.ModWiki && (zModAction = P("modaction"))!=0 ){ if( strcmp(zModAction,"delete")==0 ){ @@ -711,62 +908,117 @@ } if( strcmp(zModAction,"approve")==0 ){ moderation_approve(rid); } } +#ifdef LANG_RU + style_header("Правка \"%h\"", pWiki->zWikiTitle); +#elif LANG_EN style_header("Update of \"%h\"", pWiki->zWikiTitle); +#endif zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); zDate = db_text(0, "SELECT datetime(%.17g)", pWiki->rDate); +#ifdef LANG_RU + style_submenu_element("Как есть", "Как есть", "artifact/%S", zUuid); + style_submenu_element("История", "История", "whistory?name=%t", + pWiki->zWikiTitle); + style_submenu_element("Страница", "Страница", "wiki?name=%t", + pWiki->zWikiTitle); +#elif LANG_EN style_submenu_element("Raw", "Raw", "artifact/%S", zUuid); style_submenu_element("History", "History", "whistory?name=%t", pWiki->zWikiTitle); style_submenu_element("Page", "Page", "wiki?name=%t", pWiki->zWikiTitle); +#endif login_anonymous_available(); +#ifdef LANG_RU + @ <div class="section">Обзор</div> +#elif LANG_EN @ <div class="section">Overview</div> +#endif @ <p><table class="label-value"> +#ifdef LANG_RU + @ <tr><th>ID артефакта:</th> +#elif LANG_EN @ <tr><th>Artifact ID:</th> +#endif @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a> if( g.perm.Setup ){ @ (%d(rid)) } modPending = moderation_pending(rid); if( modPending ){ +#ifdef LANG_RU + @ <span class="modpending">*** Ожидает утверждения модератором ***</span> +#elif LANG_EN @ <span class="modpending">*** Awaiting Moderator Approval ***</span> +#endif } @ </td></tr> +#ifdef LANG_RU + @ <tr><th>Имя страницы:</th><td>%h(pWiki->zWikiTitle)</td></tr> + @ <tr><th>Дата:</th><td> + hyperlink_to_date(zDate, "</td></tr>"); + @ <tr><th>Создавший пользователь:</th><td> +#elif LANG_EN @ <tr><th>Page Name:</th><td>%h(pWiki->zWikiTitle)</td></tr> @ <tr><th>Date:</th><td> hyperlink_to_date(zDate, "</td></tr>"); @ <tr><th>Original User:</th><td> +#endif hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>"); if( pWiki->nParent>0 ){ int i; +#ifdef LANG_RU + @ <tr><th>Родител%s(pWiki->nParent==1?"ь":"и"):</th><td> +#elif LANG_EN @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td> +#endif for(i=0; i<pWiki->nParent; i++){ char *zParent = pWiki->azParent[i]; @ %z(href("info/%S",zParent))%s(zParent)</a> } @ </td></tr> } @ </table> if( g.perm.ModWiki && modPending ){ +#ifdef LANG_RU + @ <div class="section">Модерация</div> +#elif LANG_EN @ <div class="section">Moderation</div> +#endif @ <blockquote> @ <form method="POST" action="%R/winfo/%s(zUuid)"> @ <label><input type="radio" name="modaction" value="delete"> +#ifdef LANG_RU + @ Удалить это изменение</label><br /> +#elif LANG_EN @ Delete this change</label><br /> +#endif @ <label><input type="radio" name="modaction" value="approve"> +#ifdef LANG_RU + @ Утвердить это изменение</label><br /> +#elif LANG_EN @ Approve this change</label><br /> +#endif +#ifdef LANG_RU + @ <input type="submit" value="Ок"> +#elif LANG_EN @ <input type="submit" value="Submit"> +#endif @ </form> @ </blockquote> } +#ifdef LANG_RU + @ <div class="section">Содержимое</div> +#elif LANG_EN @ <div class="section">Content</div> +#endif blob_init(&wiki, pWiki->zWiki, -1); wiki_convert(&wiki, 0, 0); blob_reset(&wiki); manifest_destroy(pWiki); style_footer(); @@ -779,12 +1031,17 @@ va_list ap; const char *z; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); +#ifdef LANG_RU + style_header("Ошибка URL"); + @ <h1>Ошибка</h1> +#elif LANG_EN style_header("URL Error"); @ <h1>Error</h1> +#endif @ <p>%h(z)</p> style_footer(); } /* @@ -794,20 +1051,38 @@ static Manifest *vdiff_parse_manifest(const char *zParam, int *pRid){ int rid; *pRid = rid = name_to_rid_www(zParam); if( rid==0 ){ - const char *z = P(zParam); - if( z==0 || z[0]==0 ){ - webpage_error("Missing \"%s\" query parameter.", zParam); - }else{ - webpage_error("No such artifact: \"%s\"", z); - } - return 0; + const char *z = P(zParam); + if( z==0 || z[0]==0 ){ + webpage_error( +#ifdef LANG_RU + "Отсутствует параметр \"%s\" в запросе." +#elif LANG_EN + "Missing \"%s\" query parameter." +#endif + , zParam); + }else{ + webpage_error( +#ifdef LANG_RU + "Артефакт не найден: \"%s\"" +#elif LANG_EN + "No such artifact: \"%s\"" +#endif + , z); + } + return 0; } if( !is_a_version(rid) ){ - webpage_error("Artifact %s is not a checkin.", P(zParam)); + webpage_error( +#ifdef LANG_RU + "Артефакт не Фиксация.", +#elif LANG_EN + "Artifact %s is not a checkin.", +#endif + P(zParam)); return 0; } return manifest_get(rid, CFTYPE_MANIFEST); } @@ -840,11 +1115,15 @@ hyperlink_to_uuid(zUuid); blob_zero(&comment); db_column_blob(&q, 2, &comment); wiki_convert(&comment, 0, wikiFlags); blob_reset(&comment); +#ifdef LANG_RU + @ (пользователь: +#elif LANG_EN @ (user: +#endif hyperlink_to_user(zUser,zDate,","); if( zTagList && zTagList[0] && g.perm.Hyperlink ){ int i; const char *z = zTagList; Blob links; @@ -856,16 +1135,28 @@ href("%R/timeline?r=%#t&nd&c=%t",i,z,zDate), i,z, &z[i] ); if( z[i]==0 ) break; z += i+2; } +#ifdef LANG_RU + @ метка: %s(blob_str(&links)), +#elif LANG_EN @ tags: %s(blob_str(&links)), +#endif blob_reset(&links); }else{ - @ tags: %h(zTagList), +#ifdef LANG_RU + @ метка: %h(zTagList), +#elif LANG_EN + @ метка: %h(zTagList), +#endif } +#ifdef LANG_RU + @ дата: +#elif LANG_EN @ date: +#endif hyperlink_to_date(zDate, ")"); } db_finalize(&q); } @@ -917,25 +1208,47 @@ showDetail = atoi(PD("detail","0")); if( !showDetail && sideBySide ) showDetail = 1; zFrom = P("from"); zTo = P("to"); if( !sideBySide ){ - style_submenu_element("Side-by-side Diff", "sbsdiff", + style_submenu_element( +#ifdef LANG_RU + "Различия бок-о-бок", "sbsdiff", +#elif LANG_EN + "Side-by-side Diff", "sbsdiff", +#endif "%R/vdiff?from=%T&to=%T&detail=%d&sbs=1", zFrom, zTo, showDetail); }else{ - style_submenu_element("Unified Diff", "udiff", + style_submenu_element( +#ifdef LANG_RU + "Различия разом", "udiff", +#elif LANG_EN + "Unified Diff", "udiff", +#endif "%R/vdiff?from=%T&to=%T&detail=%d&sbs=0", zFrom, zTo, showDetail); } style_submenu_element("Invert", "invert", "%R/vdiff?from=%T&to=%T&detail=%d&sbs=%d", zTo, zFrom, showDetail, sideBySide); +#ifdef LANG_RU + style_header("Различия в фиксации"); + @ <h2>Различия между:</h2><blockquote> + checkin_description(ridFrom); +#elif LANG_EN style_header("Check-in Differences"); @ <h2>Difference From:</h2><blockquote> checkin_description(ridFrom); - @ </blockquote><h2>To:</h2><blockquote> +#endif + @ </blockquote><h2> +#ifdef LANG_RU + @ и: +#elif LANG_EN + @ To: +#endif + @ </h2><blockquote> checkin_description(ridTo); @ </blockquote> if( pRe ){ @ <p><b>Only differences that match regular expression "%h(zRe)" @ are shown.</b></p> @@ -1273,32 +1586,71 @@ content_get(v2, &c2); text_diff(&c1, &c2, pOut, pRe, diffFlags); blob_reset(&c1); blob_reset(&c2); if( !isPatch ){ +#ifdef LANG_RU + style_header("Различия"); +#elif LANG_EN style_header("Diff"); - style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch", +#endif + style_submenu_element( +#ifdef LANG_RU + "Патч", "Патч", +#elif LANG_EN + "Patch", "Patch", +#endif + "%s/fdiff?v1=%T&v2=%T&patch", g.zTop, P("v1"), P("v2")); if( !sideBySide ){ - style_submenu_element("Side-by-side Diff", "sbsdiff", + style_submenu_element( +#ifdef LANG_RU + "Различия бок-о-бок", "sbsdiff", +#elif LANG_EN + "Side-by-side Diff", "sbsdiff", +#endif "%s/fdiff?v1=%T&v2=%T&sbs=1", g.zTop, P("v1"), P("v2")); }else{ - style_submenu_element("Unified Diff", "udiff", + style_submenu_element( +#ifdef LANG_RU + "Различия разом", "udiff", +#elif LANG_EN + "Unified Diff", "udiff", +#endif "%s/fdiff?v1=%T&v2=%T&sbs=0", g.zTop, P("v1"), P("v2")); } if( P("smhdr")!=0 ){ +#ifdef LANG_RU + @ <h2>Различия между артефактом +#elif LANG_EN @ <h2>Differences From Artifact - @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a> To +#endif + @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a> +#ifdef LANG_RU + @ и +#elif LANG_EN + @ To +#endif @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>.</h2> }else{ - @ <h2>Differences From - @ Artifact %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2> +#ifdef LANG_RU + @ <h2>Различия между артефактом +#elif LANG_EN + @ <h2>Differences From Artifact +#endif + @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2> object_description(v1, 0, 0); - @ <h2>To Artifact %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2> + @ <h2> +#ifdef LANG_RU + @ и артефактом +#elif LANG_EN + @ To Artifact +#endif + @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2> object_description(v2, 0, 0); } if( pRe ){ @ <b>Only differences that match regular expression "%h(zRe)" @ are shown.</b> @@ -1416,27 +1768,55 @@ if( !g.perm.Read ){ login_needed(); return; } if( rid==0 ) fossil_redirect_home(); if( g.perm.Admin ){ const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ - style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", + style_submenu_element( +#ifdef LANG_RU + "Восстановить","Восстановить", +#elif LANG_EN + "Unshun","Unshun", +#endif + "%s/shun?uuid=%s&sub=1", g.zTop, zUuid); }else{ - style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", + style_submenu_element( +#ifdef LANG_RU + "Стереть","Стереть", +#elif LANG_EN + "Shun","Shun", +#endif + "%s/shun?shun=%s#addshun", g.zTop, zUuid); } } - style_header("Hex Artifact Content"); + style_header( +#ifdef LANG_RU + "Содержимое бинарного артефакта" +#elif LANG_EN + "Hex Artifact Content" +#endif + ); zUuid = db_text("?","SELECT uuid FROM blob WHERE rid=%d", rid); if( g.perm.Setup ){ +#ifdef LANG_RU + @ <h2>Артефакт %s(zUuid) (%d(rid)):</h2> + }else{ + @ <h2>Artifact %s(zUuid):</h2> +#elif LANG_EN @ <h2>Artifact %s(zUuid) (%d(rid)):</h2> }else{ @ <h2>Artifact %s(zUuid):</h2> +#endif } blob_zero(&downloadName); object_description(rid, 0, &downloadName); +#ifdef LANG_RU + style_submenu_element("Скачать", "Скачать", +#elif LANG_EN style_submenu_element("Download", "Download", +#endif "%s/raw/%T?name=%s", g.zTop, blob_str(&downloadName), zUuid); @ <hr /> content_get(rid, &content); @ <blockquote><pre> hexdump(&content); @@ -1573,23 +1953,42 @@ }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); } } +#ifdef LANG_RU + style_header("Содержимое артефакта"); +#elif LANG_EN style_header("Artifact Content"); +#endif zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); if( g.perm.Setup ){ +#ifdef LANG_RU + @ <h2>Артефакт %s(zUuid) (%d(rid)):</h2> + }else{ + @ <h2>Артефакт %s(zUuid):</h2> +#elif LANG_EN @ <h2>Artifact %s(zUuid) (%d(rid)):</h2> }else{ @ <h2>Artifact %s(zUuid):</h2> +#endif } + @ <blockquote><p> blob_zero(&downloadName); objType = object_description(rid, 0, &downloadName); +#ifdef LANG_RU + style_submenu_element("Скачать", "Скачать", +#elif LANG_EN style_submenu_element("Download", "Download", +#endif "%R/raw/%T?name=%s", blob_str(&downloadName), zUuid); if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ +#ifdef LANG_RU + style_submenu_element("Используется в фиксациях", "Используется в фиксациях", +#elif LANG_EN style_submenu_element("Checkins Using", "Checkins Using", +#endif "%R/timeline?uf=%s&n=200",zUuid); } asText = P("txt")!=0; zMime = mimetype_from_name(blob_str(&downloadName)); if( zMime ){ @@ -1597,20 +1996,35 @@ if( asText ){ style_submenu_element("Html", "Html", "%s/artifact/%s", g.zTop, zUuid); }else{ renderAsHtml = 1; - style_submenu_element("Text", "Text", + style_submenu_element( +#ifdef LANG_RU + "Текст", "Показать как текст", +#elif LANG_EN + "Text", "Text", +#endif "%s/artifact/%s?txt=1", g.zTop, zUuid); } }else if( fossil_strcmp(zMime, "application/x-fossil-wiki")==0 ){ if( asText ){ - style_submenu_element("Wiki", "Wiki", + style_submenu_element( +#ifdef LANG_RU + "Вики", "Показать как вики", +#elif LANG_EN + "Wiki", "Wiki", +#endif "%s/artifact/%s", g.zTop, zUuid); }else{ renderAsWiki = 1; - style_submenu_element("Text", "Text", + style_submenu_element( +#ifdef LANG_RU + "Текст", "Показать как текст", +#elif LANG_EN + "Text", "Text", +#endif "%s/artifact/%s?txt=1", g.zTop, zUuid); } } } if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){ @@ -1644,11 +2058,15 @@ }else if( strncmp(zMime, "image/", 6)==0 ){ @ <img src="%R/raw/%S(zUuid)?m=%s(zMime)" /> style_submenu_element("Image", "Image", "%R/raw/%S?m=%s", zUuid, zMime); }else{ +#ifdef LANG_RU + @ <i>(бинарный файл, байт: %d(blob_size(&content)))</i> +#elif LANG_EN @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i> +#endif } @ </blockquote> } style_footer(); } Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -493,22 +493,38 @@ " WHERE uid=%d" " AND (constant_time_cmp(pw,%Q)=0" " OR constant_time_cmp(pw,%Q)=0)", g.userUid, zSha1Pw, zPasswd) ){ sleep(1); +#ifdef LANG_RU + zErrMsg = + @ <p><span class="loginError"> + @ Вы ввели неверный старый пароль. + @ Ваш пароль не изменен. + @ </span></p> +#elif LANG_EN zErrMsg = - @ <p><span class="loginError"> - @ You entered an incorrect old password while attempting to change - @ your password. Your password is unchanged. - @ </span></p> + @ <p><span class="loginError"> + @ You entered an incorrect old password while attempting to change + @ your password. Your password is unchanged. + @ </span></p> +#endif ; }else if( fossil_strcmp(zNew1,zNew2)!=0 ){ +#ifdef LANG_RU + zErrMsg = + @ <p><span class="loginError"> + @ Два введенных новых пароля не совпадают. + @ Ваш пароль не изменен. + @ </span></p> +#elif LANG_EN zErrMsg = - @ <p><span class="loginError"> - @ The two copies of your new passwords do not match. - @ Your password is unchanged. - @ </span></p> + @ <p><span class="loginError"> + @ The two copies of your new passwords do not match. + @ Your password is unchanged. + @ </span></p> +#endif ; }else{ char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0); char *zChngPw; char *zErr; @@ -543,14 +559,21 @@ /* Attempting to log in as a user other than anonymous. */ uid = login_search_uid(zUsername, zPasswd); if( uid<=0 ){ sleep(1); +#ifdef LANG_RU zErrMsg = @ <p><span class="loginError"> + @ Вы ввели неверный пароль или имя несущестующего пользователя. + @ </span></p> +#elif LANG_EN + zErrMsg = + @ <p><span class="loginError"> @ You entered an unknown user or an incorrect password. @ </span></p> +#endif ; record_login_attempt(zUsername, zIpAddr, 0); }else{ /* Non-anonymous login is successful. Set a cookie of the form: ** @@ -561,11 +584,17 @@ */ login_set_user_cookie(zUsername, uid, NULL); redirect_to_g(); } } - style_header("Login/Logout"); + style_header( +#ifdef LANG_RU + "Вход/Выход" +#elif LANG_EN + "Login/Logout" +#endif + ); @ %s(zErrMsg) if( zGoto && P("anon")==0 ){ @ <p>A login is required for <a href="%h(zGoto)">%h(zGoto)</a>.</p> } form_begin(0, "%R/login"); @@ -572,29 +601,45 @@ if( zGoto ){ @ <input type="hidden" name="g" value="%h(zGoto)" /> } @ <table class="login_out"> @ <tr> - @ <td class="login_out_label">User ID:</td> + @ <td class="login_out_label"> +#ifdef LANG_RU + @ Пользователь: +#elif LANG_EN + @ User ID: +#endif + @ </td> if( anonFlag ){ @ <td><input type="text" id="u" name="u" value="anonymous" size="30" /></td> }else{ @ <td><input type="text" id="u" name="u" value="" size="30" /></td> } @ </tr> @ <tr> - @ <td class="login_out_label">Password:</td> + @ <td class="login_out_label"> +#ifdef LANG_RU + @ Пароль: +#elif LANG_EN + @ Password: +#endif + @ </td> @ <td><input type="password" id="p" name="p" value="" size="30" /></td> @ </tr> if( g.zLogin==0 ){ zAnonPw = db_text(0, "SELECT pw FROM user" " WHERE login='anonymous'" " AND cap!=''"); } @ <tr> @ <td></td> +#ifdef LANG_RU + @ <td><input type="submit" name="in" value="Вход" +#elif LANG_EN @ <td><input type="submit" name="in" value="Login" +#endif @ onClick="chngAction(this.form)" /></td> @ </tr> @ </table> @ <script type="text/JavaScript"> @ gebi('u').focus() @@ -608,52 +653,101 @@ @ form.action = "%h(zSSL)/login"; @ } } @ } @ </script> - if( g.zLogin==0 ){ - @ <p>Enter - }else{ - @ <p>You are currently logged in as <b>%h(g.zLogin)</b></p> - @ <p>To change your login to a different user, enter - } - @ your user-id and password at the left and press the - @ "Login" button. Your user name will be stored in a browser cookie. - @ You must configure your web browser to accept cookies in order for - @ the login to take.</p> +#ifdef LANG_RU + if( g.zLogin==0 ){ + @ <p>Укажите + }else{ + @ <p>Вы вошли как <b>%h(g.zLogin)</b></p> + @ <p>Чтобы войти под другим именем, укажите + } + @ имя пользователя и пароль, и нажмите кнопку "Вход". + @ Введенное имя будет сохранено в куках браузера. + @ Вы должны разрешить браузеру сохранять куки для возможности входа + @ на сайт.</p> +#elif LANG_EN + if( g.zLogin==0 ){ + @ <p>Enter + }else{ + @ <p>You are currently logged in as <b>%h(g.zLogin)</b></p> + @ <p>To change your login to a different user, enter + } + @ your user-id and password at the left and press the + @ "Login" button. Your user name will be stored in a browser cookie. + @ You must configure your web browser to accept cookies in order for + @ the login to take.</p> +#endif + if( db_get_boolean("self-register", 0) ){ +#ifdef LANG_RU + @ <p>Если у вас нет аккаунта, вы можете + @ <a href="%s(g.zTop)/register?g=%T(P("G"))">зарегистрироваться</a>. +#elif LANG_EN @ <p>If you do not have an account, you can @ <a href="%s(g.zTop)/register?g=%T(P("G"))">create one</a>. +#endif } + if( zAnonPw ){ unsigned int uSeed = captcha_seed(); char const *zDecoded = captcha_decode(uSeed); int bAutoCaptcha = db_get_boolean("auto-captcha", 0); char *zCaptcha = captcha_render(zDecoded); @ <p><input type="hidden" name="cs" value="%u(uSeed)" /> +#ifdef LANG_RU + @ Гости могут указать <b>anonymous</b> как имя пользователя + @ и ввести 8-символьный пароль, указанный ниже:</p> +#elif LANG_EN @ Visitors may enter <b>anonymous</b> as the user-ID with @ the 8-character hexadecimal password shown below:</p> +#endif @ <div class="captcha"><table class="captcha"><tr><td><pre> @ %h(zCaptcha) @ </pre></td></tr></table> if( bAutoCaptcha ) { - @ <input type="button" value="Fill out captcha" +#ifdef LANG_RU + @ <input type="button" value="Заполнить капчу" +#elif LANG_EN + @ <input type="button" value="Fill out captcha" +#endif @ onclick="gebi('u').value='anonymous'; gebi('p').value='%s(zDecoded)';" /> } @ </div> free(zCaptcha); } if( g.zLogin ){ @ <hr /> +#ifdef LANG_RU + @ <p>Для выхода + @ нажмите эту кнопку:<br /> + @ <input type="submit" name="out" value="Выход" /></p> +#elif LANG_EN @ <p>To log off the system (and delete your login cookie) @ press the following button:<br /> @ <input type="submit" name="out" value="Logout" /></p> +#endif } @ </form> if( g.perm.Password ){ @ <hr /> +#ifdef LANG_RU + @ <p>Для смены своего пароля введите текущий пароль и два раза + @ новый, после чего нажмите кнопку "Изменить пароль".</p> + @ <form action="login" method="post"> + @ <table> + @ <tr><td class="login_out_label">Старый пароль:</td> + @ <td><input type="password" name="p" size="30" /></td></tr> + @ <tr><td class="login_out_label">Новый пароль:</td> + @ <td><input type="password" name="n1" size="30" /></td></tr> + @ <tr><td class="login_out_label">Повторите новый пароль:</td> + @ <td><input type="password" name="n2" size="30" /></td></tr> + @ <tr><td></td> + @ <td><input type="submit" value="Изменить пароль" /></td></tr> +#elif LANG_EN @ <p>To change your password, enter your old password and your @ new password twice below then press the "Change Password" @ button.</p> form_begin(0, "%R/login"); @ <table> @@ -663,10 +757,11 @@ @ <td><input type="password" name="n1" size="30" /></td></tr> @ <tr><td class="login_out_label">Repeat New Password:</td> @ <td><input type="password" name="n2" size="30" /></td></tr> @ <tr><td></td> @ <td><input type="submit" value="Change Password" /></td></tr> +#endif @ </table> @ </form> } style_footer(); } @@ -1145,13 +1240,19 @@ if( !g.perm.Hyperlink && db_exists("SELECT 1 FROM user" " WHERE login='anonymous'" " AND cap LIKE '%%h%%'") ){ const char *zUrl = PD("REQUEST_URI", "index"); +#ifdef LANG_RU + @ <p>Многие <span class="disabled">ссылки отключены.</span><br /> + @ Используйте <a href="%s(g.zTop)/login?anon=1&g=%T(zUrl)">анонимный вход</a> + @ для включения ссылок.</p> +#elif LANG_EN @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br /> @ Use <a href="%s(g.zTop)/login?anon=1&g=%T(zUrl)">anonymous login</a> @ to enable hyperlinks.</p> +#endif } } /* ** While rendering a form, call this routine to add the Anti-CSRF token Index: src/main.mk ================================================================== --- src/main.mk +++ src/main.mk @@ -8,11 +8,11 @@ # to regenerate this file. # # This file is included by primary Makefile. # -XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) -I$(OBJDIR) +XTCC = $(TCC) $(CFLAGS) -DFOSSIL_ENABLE_JSON -DLANG_RU -I. -I$(SRCDIR) -I$(OBJDIR) SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/allrepo.c \ Index: src/manifest.c ================================================================== --- src/manifest.c +++ src/manifest.c @@ -1552,40 +1552,71 @@ if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){ zNewStatus = pManifest->aField[i].zValue; } } if( zNewStatus ){ +#ifdef LANG_RU + blob_appendf(&comment, "%h задача [%.10s]: <i>%h</i>", +#elif LANG_EN blob_appendf(&comment, "%h ticket [%.10s]: <i>%h</i>", +#endif zNewStatus, pManifest->zTicketUuid, zTitle ); if( pManifest->nField>1 ){ +#ifdef LANG_RU + blob_appendf(&comment, " плюс еще изменений %d", +#elif LANG_EN blob_appendf(&comment, " plus %d other change%s", +#endif pManifest->nField-1, pManifest->nField==2 ? "" : "s"); } +#ifdef LANG_RU + blob_appendf(&brief, "%h задача [%.10s].", +#elif LANG_EN blob_appendf(&brief, "%h ticket [%.10s].", +#endif zNewStatus, pManifest->zTicketUuid); }else{ zNewStatus = db_text("unknown", "SELECT %s FROM ticket WHERE tkt_uuid='%s'", zStatusColumn, pManifest->zTicketUuid ); +#ifdef LANG_RU + blob_appendf(&comment, "Задача [%.10s] <i>%h</i> статус остался %h с " + "%d изменением", + pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField +#elif LANG_EN blob_appendf(&comment, "Ticket [%.10s] <i>%h</i> status still %h with " "%d other change%s", - pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField, - pManifest->nField==1 ? "" : "s" + pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField, + pManifest->nField==1 ? "" : "s" +#endif ); free(zNewStatus); +#ifdef LANG_RU + blob_appendf(&brief, "Задача [%.10s]: %d изменение", + pManifest->zTicketUuid, pManifest->nField +#elif LANG_EN blob_appendf(&brief, "Ticket [%.10s]: %d change%s", pManifest->zTicketUuid, pManifest->nField, pManifest->nField==1 ? "" : "s" +#endif ); } }else{ +#ifdef LANG_RU + blob_appendf(&comment, "Новая задача [%.10s] <i>%h</i>.", +#elif LANG_EN blob_appendf(&comment, "New ticket [%.10s] <i>%h</i>.", +#endif pManifest->zTicketUuid, zTitle ); +#ifdef LANG_RU + blob_appendf(&brief, "Новая задача [%.10s].", pManifest->zTicketUuid); +#elif LANG_EN blob_appendf(&brief, "New ticket [%.10s].", pManifest->zTicketUuid); +#endif } free(zTitle); db_multi_exec( "REPLACE INTO event(type,tagid,mtime,objid,user,comment,brief)" "VALUES('t',%d,%.17g,%d,%Q,%Q,%Q)", @@ -1771,13 +1802,21 @@ ); if( prior ){ content_deltify(prior, rid, 0); } if( nWiki>0 ){ +#ifdef LANG_RU + zComment = mprintf("Правка страницы [%h]", p->zWikiTitle); +#elif LANG_EN zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle); +#endif }else{ +#ifdef LANG_RU + zComment = mprintf("Удаление страницы [%h]", p->zWikiTitle); +#elif LANG_EN zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle); +#endif } db_multi_exec( "REPLACE INTO event(type,mtime,objid,user,comment," " bgcolor,euser,ecomment)" "VALUES('w',%.17g,%d,%Q,%Q," @@ -1869,14 +1908,23 @@ || !validate16(p->zAttachTarget, UUID_SIZE) ){ char *zComment; if( p->zAttachSrc && p->zAttachSrc[0] ){ zComment = mprintf( +#ifdef LANG_RU + "Добавить вложение [%R/artifact/%S|%h] к вики-странице [%h]", +#elif LANG_EN "Add attachment [%R/artifact/%S|%h] to wiki page [%h]", +#endif p->zAttachSrc, p->zAttachName, p->zAttachTarget); }else{ - zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", + zComment = mprintf( +#ifdef LANG_RU + "Удален файл \"%h\" из страницы[%h]", +#elif LANG_EN + "Delete attachment \"%h\" from wiki page [%h]", +#endif p->zAttachName, p->zAttachTarget); } db_multi_exec( "REPLACE INTO event(type,mtime,objid,user,comment)" "VALUES('w',%.17g,%d,%Q,%Q)", @@ -1885,14 +1933,23 @@ free(zComment); }else{ char *zComment; if( p->zAttachSrc && p->zAttachSrc[0] ){ zComment = mprintf( +#ifdef LANG_RU + "Добавить вложение [%R/artifact/%S|%h] к задаче [%S]", +#elif LANG_EN "Add attachment [%R/artifact/%S|%h] to ticket [%S]", +#endif p->zAttachSrc, p->zAttachName, p->zAttachTarget); }else{ - zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]", + zComment = mprintf( +#ifdef LANG_RU + "Удален файл \"%h\" из задачи [%.10s]", +#elif LANG_EN + "Delete attachment \"%h\" from ticket [%.10s]", +#endif p->zAttachName, p->zAttachTarget); } db_multi_exec( "REPLACE INTO event(type,mtime,objid,user,comment)" "VALUES('t',%.17g,%d,%Q,%Q)", Index: src/report.c ================================================================== --- src/report.c +++ src/report.c @@ -35,11 +35,17 @@ int rn = 0; int cnt = 0; login_check_credentials(); if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; } - style_header("Ticket Main Menu"); + style_header( +#ifdef LANG_RU + "Задачи: основное меню" +#elif LANG_EN + "Ticket Main Menu" +#endif + ); if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1); zScript = ticket_reportlist_code(); if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1); blob_zero(&ril); @@ -996,10 +1002,11 @@ char *zClrKey; int tabs; Stmt q; char *zErr1 = 0; char *zErr2 = 0; + Blob bSql = empty_blob; login_check_credentials(); if( !g.perm.RdTkt ){ login_needed(); return; } rn = atoi(PD("rn","0")); if( rn==0 ){ @@ -1017,10 +1024,12 @@ zTitle = db_column_malloc(&q, 0); zSql = db_column_malloc(&q, 1); zOwner = db_column_malloc(&q, 2); zClrKey = db_column_malloc(&q, 3); db_finalize(&q); + + if( P("order_by") ){ /* ** If the user wants to do a column sort, wrap the query into a sub ** query and then sort the results. This is a whole lot easier than @@ -1032,27 +1041,48 @@ const char* zDir = PD("order_dir",""); zDir = !strcmp("ASC",zDir) ? "ASC" : "DESC"; zSql = mprintf("SELECT * FROM (%s) ORDER BY %d %s", zSql, nField, zDir); } } + Th_FossilInit(0, 0); + Th_Store("login", g.zLogin); + Th_Store("date", db_text(0, "SELECT datetime('now')")); + Th_RenderToBlob(zSql, &bSql); + zSql = bSql.aData; count = 0; if( !tabs ){ struct GenerateHTML sState; db_multi_exec("PRAGMA empty_result_callbacks=ON"); - style_submenu_element("Raw", "Raw", + style_submenu_element( +#ifdef LANG_RU + "Текстом", "Текстом", +#elif LANG_EN + "Raw", "Raw", +#endif "rptview?tablist=1&%h", PD("QUERY_STRING","")); if( g.perm.Admin || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){ - style_submenu_element("Edit", "Edit", "rptedit?rn=%d", rn); + style_submenu_element( +#ifdef LANG_RU + "Изменить", "Изменить", +#elif LANG_EN + "Edit", "Edit", +#endif + "rptedit?rn=%d", rn); } if( g.perm.TktFmt ){ style_submenu_element("SQL", "SQL", "rptsql?rn=%d",rn); } if( g.perm.NewTkt ){ - style_submenu_element("New Ticket", "Create a new ticket", + style_submenu_element( +#ifdef LANG_RU + "Новая задача", "Создать новую задачу", +#elif LANG_EN + "New Ticket", "Create a new ticket", +#endif "%s/tktnew", g.zTop); } style_header(zTitle); output_color_key(zClrKey, 1, "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\""); Index: src/sqlite3.c ================================================================== --- src/sqlite3.c +++ src/sqlite3.c @@ -14083,12 +14083,13 @@ zDate++; neg = 1; }else{ neg = 0; } - if( getDigits(zDate,4,0,9999,'-',&Y,2,1,12,'-',&M,2,1,31,0,&D)!=3 ){ - return 1; + if( getDigits(zDate,4,0,9999,'-',&Y,2,1,12,'-',&M,2,1,31,0,&D)!=3 && + getDigits(zDate,2,1,31,'.',&D,2,1,12,'.',&M,4,0,9999,0,&Y)!=3 ){ + return 1; } zDate += 10; while( sqlite3Isspace(*zDate) || 'T'==*(u8*)zDate ){ zDate++; } if( parseHhMmSs(zDate, p)==0 ){ /* We got the time */ @@ -14631,12 +14632,12 @@ ){ DateTime x; if( isDate(context, argc, argv, &x)==0 ){ char zBuf[100]; computeYMD_HMS(&x); - sqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d %02d:%02d:%02d", - x.Y, x.M, x.D, x.h, x.m, (int)(x.s)); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%02d.%02d.%04d %02d:%02d:%02d", + x.D, x.M, x.Y, x.h, x.m, (int)(x.s)); sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); } } /* Index: src/th_main.c ================================================================== --- src/th_main.c +++ src/th_main.c @@ -102,10 +102,12 @@ } } return zRc; } +static Blob* pOutBlob = NULL; + /* ** Send text to the appropriate output: Either to the console ** or to the CGI reply buffer. Escape all characters with special ** meaning to HTML if the encode parameter is true. */ @@ -114,11 +116,13 @@ if( n<0 ) n = strlen(z); if( encode ){ z = htmlize(z, n); n = strlen(z); } - if( g.cgiOutput ){ + if(pOutBlob) + blob_append(pOutBlob, z, n); + else if( g.cgiOutput ){ cgi_append_content(z, n); }else{ fwrite(z, 1, n, stdout); fflush(stdout); } @@ -918,10 +922,18 @@ }else{ sendText(z, i, 0); } return rc; } + +int Th_RenderToBlob(const char *z, Blob *pBlob){ + int result; + pOutBlob = pBlob; + result = Th_Render(z); + pOutBlob = NULL; + return result; +} /* ** COMMAND: test-th-render */ void test_th_render(void){ Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -820,11 +820,11 @@ static char *zBase = 0; static const char zBaseSql[] = @ SELECT @ blob.rid AS blobRid, @ uuid AS uuid, - @ datetime(event.mtime,'localtime') AS timestamp, + @ datetime(event.mtime, 'localtime') AS timestamp, @ coalesce(ecomment, comment) AS comment, @ coalesce(euser, user) AS user, @ blob.rid IN leaf AS leaf, @ bgcolor AS bgColor, @ event.type AS eventType, @@ -921,16 +921,32 @@ blob_zero(&out); while( db_step(&q)==SQLITE_ROW ){ const char *zFN = db_column_text(&q, 0); blob_appendf(&out, "%s%z%h</a>", zSep, href("%R/finfo?name=%t", zFN), zFN); +#ifdef LANG_RU + zSep = " или "; +#elif LANG_EN zSep = " or "; +#endif } db_finalize(&q); return blob_str(&out); } +#ifdef LANG_RU +const char* numberSuffix(int number, const char* s1, const char* s24, const char* s590){ + int lastDigit = number % 10; + if(lastDigit == 1 && number != 11) + return s1; + if(lastDigit >= 2 && lastDigit <= 4 && (number < 12 || number > 14)) + return s24; + return s590; +} +#elif LANG_EN +#define numberSuffix(n) (n==1?"":"s") +#endif /* ** WEBPAGE: timeline ** ** Query parameters: @@ -1045,11 +1061,17 @@ }else{ zUses = 0; } } - style_header("Timeline"); + style_header( +#ifdef LANG_RU + "События" +#elif LANG_EN + "Timeline" +#endif + ); login_anonymous_available(); timeline_temp_table(); blob_zero(&sql); blob_zero(&desc); blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1); @@ -1082,13 +1104,25 @@ blob_appendf(&sql, ",%d", p->rid); p = p->u.pTo; } blob_append(&sql, ")", -1); path_reset(); - blob_append(&desc, "All nodes on the path from ", -1); + blob_append(&desc, +#ifdef LANG_RU + "Все узлы с путём от ", +#elif LANG_EN + "All nodes on the path from ", +#endif + -1); blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom); - blob_append(&desc, " and ", -1); + blob_append(&desc, +#ifdef LANG_RU + " до " +#elif LANG_EN + " and " +#endif + , -1); blob_appendf(&desc, "%z[%h]</a>", href("%R/info/%h",zTo), zTo); tmFlags |= TIMELINE_DISJOINT; db_multi_exec("%s", blob_str(&sql)); }else if( (p_rid || d_rid) && g.perm.Read ){ /* If p= or d= is present, ignore all other parameters other than n= */ @@ -1108,26 +1142,48 @@ nd = 0; if( d_rid ){ compute_descendants(d_rid, nEntry+1); nd = db_int(0, "SELECT count(*)-1 FROM ok"); if( nd>=0 ) db_multi_exec("%s", blob_str(&sql)); - if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s"); + if( nd>0 ) + blob_appendf(&desc, +#ifdef LANG_RU + "%d потом%s", nd, numberSuffix(nd, "ок", "ка", "ков")); +#elif LANG_EN + "%d descendant%s", nd, numberSuffix(nd)); +#endif if( useDividers ) timeline_add_dividers(0, d_rid); db_multi_exec("DELETE FROM ok"); } if( p_rid ){ compute_ancestors(p_rid, nEntry+1, 0); np = db_int(0, "SELECT count(*)-1 FROM ok"); if( np>0 ){ - if( nd>0 ) blob_appendf(&desc, " and "); - blob_appendf(&desc, "%d ancestors", np); + if( nd>0 ) blob_appendf(&desc, +#ifdef LANG_RU + " и " +#elif LANG_EN + " and " +#endif + ); + blob_appendf(&desc, +#ifdef LANG_RU + "%d пред%s", np, numberSuffix(np, "ок", "ка", "ков")); +#elif LANG_EN + "%d ancestor%s", numberSuffix(np)); +#endif db_multi_exec("%s", blob_str(&sql)); } if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid); } - blob_appendf(&desc, " of %z[%.10s]</a>", - href("%R/info/%s", zUuid), zUuid); + blob_appendf(&desc, +#ifdef LANG_RU + " %z[%.10s]</a>", +#elif LANG_EN + " of %z[%.10s]</a>", +#endif + href("%R/info/%s", zUuid), zUuid); }else if( f_rid && g.perm.Read ){ /* If f= is present, ignore all other parameters other than n= */ char *zUuid; db_multi_exec( "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);" @@ -1137,18 +1193,29 @@ f_rid, f_rid, f_rid ); blob_appendf(&sql, " AND event.objid IN ok"); db_multi_exec("%s", blob_str(&sql)); if( useDividers ) timeline_add_dividers(0, f_rid); - blob_appendf(&desc, "Parents and children of check-in "); + blob_appendf(&desc, +#ifdef LANG_RU + "Предки и потомки фиксации " +#elif LANG_EN + "Parents and children of check-in " +#endif + ); zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid); blob_appendf(&desc, "%z[%.10s]</a>", href("%R/info/%s", zUuid), zUuid); tmFlags |= TIMELINE_DISJOINT; }else{ /* Otherwise, a timeline based on a span of time */ int n; - const char *zEType = "timeline item"; + const char *zEType = +#ifdef LANG_RU + "событие"; +#elif LANG_EN + "timeline item"; +#endif char *zDate; char *zNEntry = mprintf("%d", nEntry); url_add_parameter(&url, "n", zNEntry); if( zUses ){ blob_appendf(&sql, " AND event.objid IN usesfile "); @@ -1281,88 +1348,204 @@ } blob_appendf(&sql, " LIMIT %d", nEntry); db_multi_exec("%s", blob_str(&sql)); n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/"); - if( zAfter==0 && zBefore==0 && zCirca==0 ){ - blob_appendf(&desc, "%d most recent %ss", n, zEType); - }else{ - blob_appendf(&desc, "%d %ss", n, zEType); + { +#ifdef LANG_RU + const char* rZEtype; + const char* rSuffix; + const char* rLastTitleSuffix = numberSuffix(n, "яя", "ие", "их"); + switch(zEType[0]){ + case 'c': /*check-in*/ + rZEtype = "фиксаци"; + rSuffix = numberSuffix(n, "я", "и", "й"); + break; + case 'w': /*wiki-edit*/ + rZEtype = "вики-прав"; + rSuffix = numberSuffix(n, "ка", "ки", "ок"); + break; + case 't': /*tiket-change*/ + rZEtype = "задач"; + rSuffix = numberSuffix(n, "а", "и", ""); + break; + case 'e': /*events*/ + rZEtype = "новост"; + rSuffix = numberSuffix(n, "ь", "и", "ей"); + break; + default: + rZEtype = "событи"; + rSuffix = numberSuffix(n, "е", "я", "й"); + rLastTitleSuffix = numberSuffix(n, "ее", "их", "их"); + } +#endif + if( zAfter==0 && zBefore==0 && zCirca==0 ){ + #ifdef LANG_RU + blob_appendf(&desc, "%d последн%s %s%s", n, rLastTitleSuffix, rZEtype, rSuffix); +#elif LANG_EN + blob_appendf(&desc, "%d most recent %ss", n, zEType); +#endif + }else{ +#ifdef LANG_RU + blob_appendf(&desc, "%d %s%s", n, rZEtype, rSuffix); +#elif LANG_EN + blob_appendf(&desc, "%d %ss", n, zEType); +#endif + } } if( zUses ){ char *zFilenames = names_of_file(zUses); blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames, href("%R/artifact/%S",zUses), zUses); tmFlags |= TIMELINE_DISJOINT; } if( zUser ){ +#ifdef LANG_RU + blob_appendf(&desc, " пользователя %h", zUser); +#elif LANG_EN blob_appendf(&desc, " by user %h", zUser); +#endif tmFlags |= TIMELINE_DISJOINT; } if( zTagName ){ +#ifdef LANG_RU + blob_appendf(&desc, " с тэгом \"%h\"", zTagName); +#elif LANG_EN blob_appendf(&desc, " tagged with \"%h\"", zTagName); +#endif tmFlags |= TIMELINE_DISJOINT; }else if( zBrName ){ +#ifdef LANG_RU + blob_appendf(&desc, " ветки \"%h\"", zBrName); +#elif LANG_EN blob_appendf(&desc, " related to \"%h\"", zBrName); +#endif tmFlags |= TIMELINE_DISJOINT; } if( rAfter>0.0 ){ if( rBefore>0.0 ){ - blob_appendf(&desc, " occurring between %h and %h.<br>", - zAfter, zBefore); + blob_appendf(&desc, +#ifdef LANG_RU + " между %h and %h.<br>" +#elif LANG_EN + " occurring between %h and %h.<br>" +#endif + , zAfter, zBefore); }else{ - blob_appendf(&desc, " occurring on or after %h.<br />", zAfter); + blob_appendf(&desc, +#ifdef LANG_RU + " после %h.<br />" +#elif LANG_EN + " occurring on or after %h.<br />" +#endif + , zAfter); } }else if( rBefore>0.0 ){ +#ifdef LANG_RU + blob_appendf(&desc, " до %h.<br />", zBefore); +#elif LANG_EN blob_appendf(&desc, " occurring on or before %h.<br />", zBefore); +#endif }else if( rCirca>0.0 ){ +#ifdef LANG_RU + blob_appendf(&desc, " около %h.<br />", zCirca); +#elif LANG_EN blob_appendf(&desc, " occurring around %h.<br />", zCirca); +#endif } if( zSearch ){ +#ifdef LANG_RU + blob_appendf(&desc, " похожие на \"%h\"", zSearch); +#elif LANG_EN blob_appendf(&desc, " matching \"%h\"", zSearch); +#endif } if( g.perm.Hyperlink ){ if( zAfter || n==nEntry ){ zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/"); +#ifdef LANG_RU + timeline_submenu(&url, "Ранее", "b", zDate, "a"); +#elif LANG_EN timeline_submenu(&url, "Older", "b", zDate, "a"); +#endif free(zDate); } if( zBefore || (zAfter && n==nEntry) ){ zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/"); +#ifdef LANG_RU + timeline_submenu(&url, "Новее", "a", zDate, "b"); +#elif LANG_EN timeline_submenu(&url, "Newer", "a", zDate, "b"); +#endif free(zDate); }else if( tagid==0 ){ if( zType[0]!='a' ){ +#ifdef LANG_RU + timeline_submenu(&url, "Все типы", "y", "all", 0); +#elif LANG_EN timeline_submenu(&url, "All Types", "y", "all", 0); +#endif } if( zType[0]!='w' && g.perm.RdWiki ){ +#ifdef LANG_RU + timeline_submenu(&url, "Только вики-правки", "y", "w", 0); +#elif LANG_EN timeline_submenu(&url, "Wiki Only", "y", "w", 0); +#endif } if( zType[0]!='c' && g.perm.Read ){ +#ifdef LANG_RU + timeline_submenu(&url, "Только фиксации", "y", "ci", 0); +#elif LANG_EN timeline_submenu(&url, "Checkins Only", "y", "ci", 0); +#endif } if( zType[0]!='t' && g.perm.RdTkt ){ +#ifdef LANG_RU + timeline_submenu(&url, "Только задачи", "y", "t", 0); +#elif LANG_EN timeline_submenu(&url, "Tickets Only", "y", "t", 0); +#endif } if( zType[0]!='e' && g.perm.RdWiki ){ +#ifdef LANG_RU + timeline_submenu(&url, "Только новости", "y", "e", 0); +#elif LANG_EN timeline_submenu(&url, "Events Only", "y", "e", 0); +#endif } if( zType[0]!='g' && g.perm.Read ){ timeline_submenu(&url, "Tags Only", "y", "g", 0); } } if( nEntry>20 ){ +#ifdef LANG_RU + timeline_submenu(&url, "20 событий", "n", "20", 0); +#elif LANG_EN timeline_submenu(&url, "20 Entries", "n", "20", 0); +#endif } if( nEntry<200 ){ +#ifdef LANG_RU + timeline_submenu(&url, "200 событий", "n", "200", 0); +#elif LANG_EN timeline_submenu(&url, "200 Entries", "n", "200", 0); +#endif } if( zType[0]=='a' || zType[0]=='c' ){ if( tmFlags & TIMELINE_FCHANGES ){ +#ifdef LANG_RU + timeline_submenu(&url, "Не показывать файлы", "fc", 0, 0); +#elif LANG_EN timeline_submenu(&url, "Hide Files", "fc", 0, 0); +#endif }else{ +#ifdef LANG_RU + timeline_submenu(&url, "Показывать файлы", "fc", "", 0); +#elif LANG_EN timeline_submenu(&url, "Show Files", "fc", "", 0); +#endif } } } } if( P("showsql") ){ Index: src/tkt.c ================================================================== --- src/tkt.c +++ src/tkt.c @@ -422,37 +422,82 @@ const char *zUuid = PD("name",""); login_check_credentials(); if( !g.perm.RdTkt ){ login_needed(); return; } if( g.perm.WrTkt || g.perm.ApndTkt ){ - style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T", + style_submenu_element( +#ifdef LANG_RU + "Правка", "Изменить задачу", +#elif LANG_EN + "Edit", "Edit The Ticket", +#endif + "%s/tktedit?name=%T", g.zTop, PD("name","")); } if( g.perm.Hyperlink ){ - style_submenu_element("History", "History Of This Ticket", + style_submenu_element( +#ifdef LANG_RU + "История", "Исория задачи", +#elif LANG_EN + "History", "History Of This Ticket", +#endif "%s/tkthistory/%T", g.zTop, zUuid); - style_submenu_element("Timeline", "Timeline Of This Ticket", + style_submenu_element( +#ifdef LANG_RU + "События", "События по этой задаче", +#elif LANG_EN + "Timeline", "Timeline Of This Ticket", +#endif "%s/tkttimeline/%T", g.zTop, zUuid); - style_submenu_element("Check-ins", "Check-ins Of This Ticket", + style_submenu_element( +#ifdef LANG_RU + "Фиксации", "Фиксации по этой задаче", +#elif LANG_EN + "Check-ins", "Check-ins Of This Ticket", +#endif "%s/tkttimeline/%T?y=ci", g.zTop, zUuid); } if( g.perm.NewTkt ){ - style_submenu_element("New Ticket", "Create a new ticket", + style_submenu_element( +#ifdef LANG_RU + "Новая задача", "Создать новую задачу", +#elif LANG_EN + "New Ticket", "Create a new ticket", +#endif "%s/tktnew", g.zTop); } if( g.perm.ApndTkt && g.perm.Attach ){ - style_submenu_element("Attach", "Add An Attachment", + style_submenu_element( +#ifdef LANG_RU + "Добавить файл", "Добавить файл к задаче", +#elif LANG_EN + "Attach", "Add An Attachment", +#endif "%s/attachadd?tkt=%T&from=%s/tktview/%t", g.zTop, zUuid, g.zTop, zUuid); } if( P("plaintext") ){ +#ifdef LANG_RU + style_submenu_element("Форматировано", "Форматировано", "%R/tktview/%S", zUuid); +#elif LANG_EN style_submenu_element("Formatted", "Formatted", "%R/tktview/%S", zUuid); +#endif }else{ +#ifdef LANG_RU + style_submenu_element("Текстом", "Текстом", +#elif LANG_EN style_submenu_element("Plaintext", "Plaintext", +#endif "%R/tktview/%S?plaintext", zUuid); } - style_header("View Ticket"); + style_header( +#ifdef LANG_RU + "Просмотр задачи" +#elif LANG_EN + "View Ticket" +#endif + ); if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1); ticket_init(); initializeVariablesFromCGI(); getAllTicketFields(); initializeVariablesFromDb(); @@ -464,11 +509,15 @@ zFullName = db_text(0, "SELECT tkt_uuid FROM ticket" " WHERE tkt_uuid GLOB '%q*'", zUuid); if( zFullName ){ +#ifdef LANG_RU + attachment_list(zFullName, "<hr /><h2>Вложения:</h2><ul>"); +#elif LANG_EN attachment_list(zFullName, "<hr /><h2>Attachments:</h2><ul>"); +#endif } style_footer(); } @@ -560,11 +609,15 @@ int nJ = 0; Blob tktchng, cksum; login_verify_csrf_secret(); if( !captcha_is_correct() ){ +#ifdef LANG_RU + @ <p class="generalError">Ошибка: неверный код.</p> +#elif LANG_EN @ <p class="generalError">Error: Incorrect security code.</p> +#endif return TH_OK; } zUuid = (const char *)pUuid; blob_zero(&tktchng); zDate = date_in_standard_format("now"); @@ -581,10 +634,16 @@ const char *zValue; int nValue; if( aField[i].zAppend ) continue; zValue = Th_Fetch(aField[i].zName, &nValue); if( zValue ){ + const char* zTemp = wiki_sanify(zValue); + if(zTemp != zValue) + { + zValue = zTemp; + nValue = strlen(zValue); + } while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; } if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0) || memcmp(zValue, aField[i].zValue, nValue)!=0 || strlen(aField[i].zValue)!=nValue ){ @@ -652,11 +711,17 @@ login_check_credentials(); if( !g.perm.NewTkt ){ login_needed(); return; } if( P("cancel") ){ cgi_redirect("home"); } - style_header("New Ticket"); + style_header( +#ifdef LANG_RU + "Новая задача" +#elif LANG_EN + "New Ticket" +#endif + ); if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1); ticket_init(); initializeVariablesFromCGI(); getAllTicketFields(); initializeVariablesFromDb(); @@ -703,26 +768,44 @@ if( !g.perm.ApndTkt && !g.perm.WrTkt ){ login_needed(); return; } zName = P("name"); if( P("cancel") ){ cgi_redirectf("tktview?name=%T", zName); } - style_header("Edit Ticket"); + style_header( +#ifdef LANG_RU + "Изменение задачи" +#elif LANG_EN + "Edit Ticket" +#endif + ); if( zName==0 || (nName = strlen(zName))<4 || nName>UUID_SIZE || !validate16(zName,nName) ){ - @ <span class="tktError">Not a valid ticket id: \"%h(zName)\"</span> +#ifdef LANG_RU + @ <span class="tktError">Неверный ID задачи: \"%h(zName)\"</span> +#elif LANG_EN + @ <span class="tktError">Not a valid ticket id: \"%h(zName)\"</span> +#endif style_footer(); return; } nRec = db_int(0, "SELECT count(*) FROM ticket WHERE tkt_uuid GLOB '%q*'", zName); if( nRec==0 ){ +#ifdef LANG_RU + @ <span class="tktError">Не найдена задача: \"%h(zName)\"</span> +#elif LANG_EN @ <span class="tktError">No such ticket: \"%h(zName)\"</span> +#endif style_footer(); return; } if( nRec>1 ){ +#ifdef LANG_RU + @ <span class="tktError">Несколько задач (%d(nRec)) начинаются с: +#elif LANG_EN @ <span class="tktError">%d(nRec) tickets begin with: +#endif @ \"%h(zName)\"</span> style_footer(); return; } if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1); @@ -803,33 +886,69 @@ login_check_credentials(); if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } zUuid = PD("name",""); zType = PD("y","a"); if( zType[0]!='c' ){ - style_submenu_element("Check-ins", "Check-ins", + style_submenu_element( +#ifdef LANG_RU + "Фиксации", "Фиксации", +#elif LANG_EN + "Check-ins", "Check-ins", +#endif "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); }else{ - style_submenu_element("Timeline", "Timeline", + style_submenu_element( +#ifdef LANG_RU + "События", "События", +#elif LANG_EN + "Timeline", "Timeline", +#endif "%s/tkttimeline?name=%T", g.zTop, zUuid); } - style_submenu_element("History", "History", + style_submenu_element( +#ifdef LANG_RU + "История", "История", +#elif LANG_EN + "History", "History", +#endif "%s/tkthistory/%s", g.zTop, zUuid); - style_submenu_element("Status", "Status", + style_submenu_element( +#ifdef LANG_RU + "Статус", "Статус", +#elif LANG_EN + "Status", "Status", +#endif "%s/info/%s", g.zTop, zUuid); if( zType[0]=='c' ){ - zTitle = mprintf("Check-Ins Associated With Ticket %h", zUuid); + zTitle = mprintf( +#ifdef LANG_RU + "Фиксации, связанные с задачей %h", +#elif LANG_EN + "Check-Ins Associated With Ticket %h", +#endif + zUuid); }else{ - zTitle = mprintf("Timeline Of Ticket %h", zUuid); + zTitle = mprintf( +#ifdef LANG_RU + "События, связанные с задачей %h", +#elif LANG_EN + "Timeline Of Ticket %h", +#endif + zUuid); } style_header(zTitle); free(zTitle); sqlite3_snprintf(6, zGlobPattern, "%s", zUuid); canonical16(zGlobPattern, strlen(zGlobPattern)); tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid); if( tagid==0 ){ +#ifdef LANG_RU + @ Задача %h(zUuid) не найдена. +#elif LANG_EN @ No such ticket: %h(zUuid) +#endif style_footer(); return; } zFullUuid = db_text(0, "SELECT substr(tagname, 5) FROM tag WHERE tagid=%d", tagid); @@ -876,16 +995,35 @@ int nChng = 0; login_check_credentials(); if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } zUuid = PD("name",""); - zTitle = mprintf("History Of Ticket %h", zUuid); - style_submenu_element("Status", "Status", + zTitle = mprintf( +#ifdef LANG_RU + "История задачи %h", +#elif LANG_EN + "History Of Ticket %h", +#endif + zUuid); + style_submenu_element( +#ifdef LANG_RU + "Статус", "Статус", +#elif LANG_EN + "Status", "Status", +#endif "%s/info/%s", g.zTop, zUuid); - style_submenu_element("Check-ins", "Check-ins", +#ifdef LANG_RU + style_submenu_element("Фиксации", "Фиксации", +#elif LANG_EN + style_submenu_element("Check-ins", "Check-ins", +#endif "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid); - style_submenu_element("Timeline", "Timeline", +#ifdef LANG_RU + style_submenu_element("События", "События", +#elif LANG_EN + style_submenu_element("Timeline", "Timeline", +#endif "%s/tkttimeline?name=%s", g.zTop, zUuid); if( P("plaintext")!=0 ){ style_submenu_element("Formatted", "Formatted", "%R/tkthistory/%S", zUuid); }else{ @@ -895,11 +1033,15 @@ style_header(zTitle); free(zTitle); tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid); if( tagid==0 ){ +#ifdef LANG_RU + @ Задача %h(zUuid) не найдена +#elif LANG_EN @ No such ticket: %h(zUuid) +#endif style_footer(); return; } db_prepare(&q, "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" @@ -930,28 +1072,48 @@ if( zFile!=0 ){ const char *zSrc = db_column_text(&q, 3); const char *zUser = db_column_text(&q, 5); if( zSrc==0 || zSrc[0]==0 ){ @ +#ifdef LANG_RU + @ <li>Удалено вложение "%h(zFile)" +#elif LANG_EN @ <li><p>Delete attachment "%h(zFile)" +#endif }else{ @ +#ifdef LANG_RU + @ <li><p>Добавлено вложение +#elif LANG_EN @ <li><p>Add attachment - @ "%z(href("%R/artifact/%S",zSrc))%h(zFile)</a>" +#endif + @ "%z(href("%R/artifact/%S",zSrc))%h(zFile)</a>" } @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>] +#ifdef LANG_RU + @ (rid %d(rid)) пользователем + hyperlink_to_user(zUser,zDate," "); +#elif LANG_EN @ (rid %d(rid)) by hyperlink_to_user(zUser,zDate," on"); +#endif hyperlink_to_date(zDate, ".</p>"); }else{ pTicket = manifest_get(rid, CFTYPE_TICKET); if( pTicket ){ @ +#ifdef LANG_RU + @ <li>Задача изменена + @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] + @ (rid %d(rid)) пользователем + hyperlink_to_user(pTicket->zUser,zDate," "); +#elif LANG_EN @ <li><p>Ticket change @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>] @ (rid %d(rid)) by hyperlink_to_user(pTicket->zUser,zDate," on"); +#endif hyperlink_to_date(zDate, ":"); @ </p> ticket_output_change_artifact(pTicket, "a"); } manifest_destroy(pTicket); Index: src/url.c ================================================================== --- src/url.c +++ src/url.c @@ -380,18 +380,29 @@ ** Prompt the user for the password for g.urlUser. Store the result ** in g.urlPasswd. */ void url_prompt_for_password(void){ if( isatty(fileno(stdin)) ){ - char *zPrompt = mprintf("\rpassword for %s: ", g.urlUser); + char *zPrompt = mprintf( +#ifdef LANG_RU + "\rПароль для %s: ", +#elif LANG_EN + "\rpassword for %s: ", +#endif + g.urlUser); Blob x; prompt_for_password(zPrompt, &x, 0); free(zPrompt); g.urlPasswd = mprintf("%b", &x); blob_reset(&x); }else{ - fossil_fatal("missing or incorrect password for user \"%s\"", + fossil_fatal( +#ifdef LANG_RU + "Отсутствует или неверен пароль для пользователя \"%s\"", +#elif LANG_EN + "missing or incorrect password for user \"%s\"", +#endif g.urlUser); } } /* Preemptively prompt for a password if a username is given in the Index: src/user.c ================================================================== --- src/user.c +++ src/user.c @@ -49,12 +49,11 @@ ** getpass for Windows */ static char *getpass(const char *prompt){ static char pwd[64]; size_t i; - - fputs(prompt,stderr); + fossil_puts(prompt,1); fflush(stderr); for(i=0; i<sizeof(pwd)-1; ++i){ pwd[i] = _getch(); if(pwd[i]=='\r' || pwd[i]=='\n'){ break; @@ -115,13 +114,25 @@ blob_zero(&secondTry); while(1){ prompt_for_passphrase(zPrompt, pPassphrase); if( verify==0 ) break; if( verify==1 && blob_size(pPassphrase)==0 ) break; - prompt_for_passphrase("Retype new password: ", &secondTry); + prompt_for_passphrase( +#ifdef LANG_RU + "��������� ����� ������: ", +#elif LANG_EN + "Retype new password: ", +#endif + &secondTry); if( blob_compare(pPassphrase, &secondTry) ){ - fossil_print("Passphrases do not match. Try again...\n"); + fossil_print( +#ifdef LANG_RU + "������ �� ���������. ���������� ��� ���...\n" +#elif LANG_EN + "Passphrases do not match. Try again...\n" +#endif + ); }else{ break; } } blob_reset(&secondTry); Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -47,28 +47,44 @@ /* ** Output rules for well-formed wiki pages */ static void well_formed_wiki_name_rules(void){ +#ifdef LANG_RU + @ <ul> + @ <li>Не должно начинаться или заканчиваться пробелом.</li> + @ <li> Не должно содержать управляющих символов, включая табуляцию + @ и перевод строки.</li> + @ <li> Не должно быть двух и более пробелов подряд.</li> + @ <li> Длина от 3 до 100 символов.</li> + @ </ul> +#elif LANG_EN @ <ul> @ <li> Must not begin or end with a space.</li> @ <li> Must not contain any control characters, including tab or @ newline.</li> @ <li> Must not have two or more spaces in a row internally.</li> @ <li> Must be between 3 and 100 characters in length.</li> @ </ul> +#endif } /* ** Check a wiki name. If it is not well-formed, then issue an error ** and return true. If it is well-formed, return false. */ static int check_name(const char *z){ if( !wiki_name_is_wellformed((const unsigned char *)z) ){ +#ifdef LANG_RU + style_header("Ошибка в названии страницы"); + @ "<span class="wikiError">%h(z)</span>" не может быть названием страницы. + @ Правила для названий страниц: +#elif LANG_EN style_header("Wiki Page Name Error"); @ The wiki name "<span class="wikiError">%h(z)</span>" is not well-formed. @ Rules for wiki page names: +#endif well_formed_wiki_name_rules(); style_footer(); return 1; } return 0; @@ -101,17 +117,27 @@ cgi_set_parameter_nocopy("name", g.zExtra); g.isHome = 1; wiki_page(); return; } +#ifdef LANG_RU + style_header("Домашняя страница"); + @ <p>Это фиктивная домашняя страница проекта. + @ Для заполнения этой страницы сначала перейдите в + @ <a href="%s(g.zTop)/setup_config">setup/config</a> + @ и задайте имя проекта. Затем создайте страницу с + @ таким же названием. Содержимое созданной страницы + @ будет отображаться вместо этого сообщения.</p> +#elif LANG_EN style_header("Home"); @ <p>This is a stub home-page for the project. @ To fill in this page, first go to @ %z(href("%R/setup_config"))setup/config</a> @ and establish a "Project Name". Then create a @ wiki page with that name. The content of that wiki page @ will be displayed in place of this message.</p> +#endif style_footer(); } /* ** Return true if the given pagename is the name of the sandbox @@ -131,43 +157,77 @@ int isSandbox; char *zUuid; Blob wiki; Manifest *pWiki = 0; const char *zPageName; +#ifdef LANG_RU + char *zBody = mprintf("%s","<i>Пустая страница</i>"); +#elif LANG_EN char *zBody = mprintf("%s","<i>Empty Page</i>"); - +#endif login_check_credentials(); if( !g.perm.RdWiki ){ login_needed(); return; } zPageName = P("name"); if( zPageName==0 ){ +#ifdef LANG_RU + style_header("Вики"); + @ <ul> +#elif LANG_EN style_header("Wiki"); @ <ul> +#endif { char *zHomePageName = db_get("project-name",0); if( zHomePageName ){ @ <li> %z(href("%R/wiki?name=%t",zHomePageName)) +#ifdef LANG_RU + @ %h(zHomePageName)</a> - домашняя страница.</li> +#elif LANG_EN @ %h(zHomePageName)</a> wiki home page.</li> +#endif } } +#ifdef LANG_RU + @ <li> %z(href("%R/timeline?y=w"))Последние правки</a></li> + @ <li> %z(href("%R/wiki_rules"))Правила форматирования</a></li> + @ <li> Используйте %z(href("%R/wiki?name=Sandbox"))песочницу</a> для экспериментов.</li> +#elif LANG_EN @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> @ <li> %z(href("%R/wiki_rules"))Formatting rules</a> for wiki.</li> @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a> @ to experiment.</li> +#endif if( g.perm.NewWiki ){ +#ifdef LANG_RU + @ <li> Создать %z(href("%R/wikinew"))новую страницу</a>.</li> +#elif LANG_EN @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li> +#endif if( g.perm.Write ){ +#ifdef LANG_RU + @ <li> Создать %z(href("%R/eventedit"))новое событие</a>.</li> +#elif LANG_EN @ <li> Create a %z(href("%R/eventedit"))new event</a>.</li> +#endif } } +#ifdef LANG_RU + @ <li> %z(href("%R/wcontent"))Все страницы</a> + @ на сервере.</li> + @ <li> <form method="get" action="%s(g.zTop)/wfind"><div> + @ Поиск по названиям: <input type="text" name="title"/> + @ <input type="submit" value = "Искать" /></div></form> +#elif LANG_EN @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> @ available on this server.</li> if( g.perm.ModWiki ){ @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li> } @ <li> form_begin(0, "%R/wfind"); @ <div>Search wiki titles: <input type="text" name="title"/> @ <input type="submit" /></div></form> +#endif @ </li> @ </ul> style_footer(); return; } @@ -197,30 +257,57 @@ style_submenu_element("Details", "Details", "%R/info/%S", zUuid); } if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){ if( db_get_boolean("wysiwyg-wiki", 0) ){ - style_submenu_element("Edit", "Edit Wiki Page", - "%s/wikiedit?name=%T&wysiwyg=1", + style_submenu_element( +#ifdef LANG_RU + "Правка", "Редактировать вики-страницу" +#elif LANG_EN + "Edit", "Edit Wiki Page" +#endif + , "%s/wikiedit?name=%T&wysiwyg=1", g.zTop, zPageName); }else{ - style_submenu_element("Edit", "Edit Wiki Page", - "%s/wikiedit?name=%T", + style_submenu_element( +#ifdef LANG_RU + "Правка", "Редактировать вики-страницу" +#elif LANG_EN + "Edit", "Edit Wiki Page" +#endif + , "%s/wikiedit?name=%T", g.zTop, zPageName); } } if( rid && g.perm.ApndWiki && g.perm.Attach ){ - style_submenu_element("Attach", "Add An Attachment", + style_submenu_element( +#ifdef LANG_RU + "Добавить файл", "Добавить файл к странице", +#elif LANG_EN + "Attach", "Add An Attachment", +#endif "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", g.zTop, zPageName, g.zTop, zPageName); } if( rid && g.perm.ApndWiki ){ - style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T", + style_submenu_element( +#ifdef LANG_RU + "Комментировать", "Добавить комментарий к странице", +#elif LANG_EN + "Append", "Add A Comment", +#endif + "%s/wikiappend?name=%T", g.zTop, zPageName); } if( g.perm.Hyperlink ){ - style_submenu_element("History", "History", "%s/whistory?name=%T", + style_submenu_element( +#ifdef LANG_RU + "История", "Показать историю правок", +#elif LANG_EN + "History", "History", +#endif + "%s/whistory?name=%T", g.zTop, zPageName); } } style_set_current_page("%s?name=%T", g.zPath, zPageName); style_header(zPageName); @@ -453,19 +540,19 @@ const char *zRemark; char *zId; zDate = db_text(0, "SELECT datetime('now')"); zId = db_text(0, "SELECT lower(hex(randomblob(8)))"); - blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>On %s UTC %h", + blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>%s %h", zId, zDate, g.zLogin); free(zDate); zUser = PD("u",g.zLogin); if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){ - blob_appendf(p, " (claiming to be %h)", zUser); + blob_appendf(p, ", представившись как %h,", zUser); } - zRemark = PD("r",""); - blob_appendf(p, " added:</i><br />\n%s</div id=\"%s\">", zRemark, zId); + zRemark = wiki_sanify(PD("r","")); + blob_appendf(p, " добавил:</i><br />\n%s</div id=\"%s\">", zRemark, zId); } /* ** WEBPAGE: wikiappend ** URL: /wikiappend?name=PAGENAME @@ -706,16 +793,30 @@ Stmt q; int showAll = P("all")!=0; login_check_credentials(); if( !g.perm.RdWiki ){ login_needed(); return; } +#ifdef LANG_RU + style_header("Доступные вики-стариницы"); +#elif LANG_EN style_header("Available Wiki Pages"); +#endif + if( showAll ){ +#ifdef LANG_RU + style_submenu_element("Активные", "Только активные страницы", "%s/wcontent", g.zTop); +#elif LANG_EN style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); +#endif }else{ +#ifdef LANG_RU + style_submenu_element("Все", "Все страницы", "%s/wcontent?all=1", g.zTop); +#elif LANG_EN style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); +#endif } + @ <ul> wiki_prepare_page_list(&q); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); int size = db_column_int(&q, 1); Index: src/wikiformat.c ================================================================== --- src/wikiformat.c +++ src/wikiformat.c @@ -234,11 +234,11 @@ #define MUTYPE_HYPERLINK 0x0400 /* <a> */ /* ** These markup types must have an end tag. */ -#define MUTYPE_STACK (MUTYPE_BLOCK | MUTYPE_FONT | MUTYPE_LIST | MUTYPE_TABLE) +#define MUTYPE_STACK (MUTYPE_BLOCK | MUTYPE_LIST | MUTYPE_TABLE) /* ** This markup types are allowed for "inline" text. */ #define MUTYPE_INLINE (MUTYPE_FONT | MUTYPE_HYPERLINK) @@ -381,10 +381,11 @@ #define TOKEN_NUM_LI 7 /* " # " */ #define TOKEN_ENUM 8 /* " \(?\d+[.)]? " */ #define TOKEN_INDENT 9 /* " " */ #define TOKEN_RAW 10 /* Output exactly (used when wiki-use-html==1) */ #define TOKEN_TEXT 11 /* None of the above */ +#define TOKEN_SWHTML 12 /* switch use html */ /* ** State flags. Save the lower 16 bits for the WIKI_* flags. */ #define AT_NEWLINE 0x0010000 /* At start of a line */ @@ -632,17 +633,21 @@ static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){ int n; if( z[0]=='<' ){ n = markupLength(z); if( n>0 ){ - *pTokenType = TOKEN_MARKUP; + if(n == 3 && z[1] == 'x') + *pTokenType = TOKEN_SWHTML; + else + *pTokenType = TOKEN_MARKUP; return n; - }else{ + } else { *pTokenType = TOKEN_CHARACTER; return 1; } } + if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){ *pTokenType = TOKEN_CHARACTER; return 1; } if( (p->state & ALLOW_WIKI)!=0 ){ @@ -701,10 +706,14 @@ static int nextRawToken(const char *z, Renderer *p, int *pTokenType){ int n; if( z[0]=='[' && (n = linkLength(z))>0 ){ *pTokenType = TOKEN_LINK; return n; + } + if( z[0]=='<' && z[1]=='x' && z[2] == '>') { + *pTokenType = TOKEN_SWHTML; + return 3; } *pTokenType = TOKEN_RAW; return 1 + textLength(z+1, p->state); } @@ -821,10 +830,12 @@ if( p->endTag ){ blob_appendf(pOut, "</%s>", aMarkup[p->iCode].zName); }else{ blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName); for(i=0; i<p->nAttr; i++){ + if(p->aAttr[i].iACode==ATTR_HREF && 0 == fossil_strnicmp(p->aAttr[i].zValue, "JavaScript:", sizeof("JavaScript:") - 1)) + continue; blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iACode].zName); if( p->aAttr[i].zValue ){ const char *zVal = p->aAttr[i].zValue; if( p->aAttr[i].iACode==ATTR_SRC && zVal[0]=='/' ){ blob_appendf(pOut, "=\"%s%s\"", g.zTop, zVal); @@ -926,11 +937,11 @@ static void popStack(Renderer *p){ if( p->nStack ){ int iCode; p->nStack--; iCode = p->aStack[p->nStack].iCode; - if( iCode!=MARKUP_DIV && p->pOut ){ + if( /*iCode!=MARKUP_DIV &&*/ p->pOut ){ blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName); } } } @@ -1273,23 +1284,28 @@ int n; int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0; int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0; int linksOnly = (p->state & WIKI_LINKSONLY)!=0; char *zOrig = z; + int forceUseHtml = 0; /* Make sure the attribute constants and names still align ** following changes in the attribute list. */ assert( fossil_strcmp(aAttribute[ATTR_WIDTH].zName, "width")==0 ); while( z[0] ){ - if( wikiHtmlOnly ){ + if( wikiHtmlOnly || forceUseHtml){ n = nextRawToken(z, p, &tokenType); }else{ n = nextWikiToken(z, p, &tokenType); } p->state &= ~(AT_NEWLINE|AT_PARAGRAPH); switch( tokenType ){ + case TOKEN_SWHTML: { + forceUseHtml = !forceUseHtml; + break; + } case TOKEN_PARAGRAPH: { if( inlineOnly ){ /* blob_append(p->pOut, " ¶ ", -1); */ blob_append(p->pOut, " ", -1); }else{ @@ -1521,18 +1537,22 @@ /* Do nothing */ }else /* Generate end-tags */ if( markup.endTag ){ - popStackToTag(p, markup.iCode); + if(markup.iType==MUTYPE_FONT) + renderMarkup(p->pOut, &markup); + else + popStackToTag(p, markup.iCode); }else /* Push <div> markup onto the stack together with the id=ID attribute. */ if( markup.iCode==MARKUP_DIV ){ pushStackWithId(p, markup.iCode, markupId(&markup), (p->state & ALLOW_WIKI)!=0); + renderMarkup(p->pOut, &markup); }else /* Enter <verbatim> processing. With verbatim enabled, all other ** markup other than the corresponding end-tag with the same ID is ** ignored. @@ -1593,13 +1613,14 @@ renderMarkup(p->pOut, &markup); pushStack(p, markup.iCode); } }else { - if( markup.iType==MUTYPE_FONT ){ + /*if( markup.iType==MUTYPE_FONT ){ startAutoParagraph(p); - }else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){ + }else*/ + if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){ p->wantAutoParagraph = 0; } if( markup.iCode==MARKUP_HR || markup.iCode==MARKUP_H1 || markup.iCode==MARKUP_H2 @@ -1907,10 +1928,41 @@ } z += n; } free(renderer.aStack); } +/* +** Test text before it add to wiki. Allow use of <x> only for developers or wikieditors. +*/ +const char* wiki_sanify(const char *pSrc){ + static Blob tempStore = BLOB_INITIALIZER; + int hasReplaces = 0; + const char* z = pSrc; + Renderer r; + + blob_reset(&tempStore); + + if(g.perm.Admin || g.perm.NewWiki || g.perm.Write || g.perm.WrWiki || g.perm.Setup) + return pSrc; + r.state = 1; + while(*z) + { + int tokenType; + int n = nextRawToken(z, &r, &tokenType); + if(tokenType == TOKEN_SWHTML) { + if(!hasReplaces) { + blob_append(&tempStore, pSrc, z - pSrc); + hasReplaces = 1; + } + blob_append(&tempStore, "<x>", 9); + } + else if(hasReplaces) + blob_append(&tempStore, z, n); + z+=n; + } + return hasReplaces ? blob_str(&tempStore) : pSrc; +} /* ** Get the next HTML token. ** ** z points to the start of a token. Return the number of