Explorar el Código

Merge pull request #469 from 2DU/master

Master
Surplus_Up (2DU) hace 8 años
padre
commit
138622329c
Se han modificado 10 ficheros con 676 adiciones y 581 borrados
  1. 220 242
      app.py
  2. 129 163
      func.py
  3. 0 42
      language/en-UK.json
  4. 54 11
      language/en-US.json
  5. 102 50
      language/ko-KR.json
  6. 18 0
      language/test.py
  7. 20 6
      mark.py
  8. 100 41
      set_mark/start.py
  9. 11 13
      set_mark/tool.py
  10. 22 13
      views/acme/index.html

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 220 - 242
app.py


+ 129 - 163
func.py

@@ -1,22 +1,16 @@
 # 모듈들 불러옴
-from css_html_js_minify import html_minify, js_minify, css_minify
-from flask import session, render_template
-from urllib import parse
-
+import css_html_js_minify
+import flask
 import json
 import sqlite3
 import hashlib
 import requests
 import re
 import html
-import time
 import os
 
 # 일부 툴 불러옴
-from set_mark.tool import get_time
-from set_mark.tool import ip_check
-from set_mark.tool import url_pas
-from set_mark.tool import sha224
+from set_mark.tool import *
 
 # 나무마크 불러옴
 from mark import *
@@ -25,12 +19,19 @@ from mark import *
 json_data = open(os.path.join('language', 'en-US.json'), 'rt', encoding='utf-8').read()
 else_lang = json.loads(json_data)
 
-def captcha_get(conn):
+def load_conn(data):
+    global conn
+    global curs
+
+    conn = data
     curs = conn.cursor()
 
+    load_conn2(data)
+
+def captcha_get():
     data = ''
 
-    if custom(conn)[2] == 0:
+    if custom()[2] == 0:
         curs.execute('select data from other where name = "recaptcha"')
         recaptcha = curs.fetchall()
         if recaptcha and recaptcha[0][0] != '':
@@ -41,11 +42,9 @@ def captcha_get(conn):
 
     return data
 
-def captcha_post(test, conn, num = 1):
-    curs = conn.cursor()
-
+def captcha_post(test, num = 1):
     if num == 1:
-        if custom(conn)[2] == 0 and captcha_get(conn) != '':
+        if custom()[2] == 0 and captcha_get() != '':
             curs.execute('select data from other where name = "sec_re"')
             sec_re = curs.fetchall()
             if sec_re and sec_re[0][0] != '':
@@ -65,53 +64,69 @@ def captcha_post(test, conn, num = 1):
     else:
         pass
 
-def load_lang(lang, data):
-    if data in lang:
-        return lang[data]
+def load_lang(data):
+    global lang
+
+    try:
+        if lang:
+            pass
+    except:
+        curs.execute("select data from other where name = 'language'")
+        rep_data = curs.fetchall()
+
+        json_data = open(os.path.join('language', rep_data[0][0] + '.json'), 'rt', encoding='utf-8').read()
+        lang = json.loads(json_data)
+
+    if data == 'please_all':
+        return lang
     else:
-        return else_lang[data]
+        if data in lang:
+            return lang[data]
+        else:
+            return else_lang[data]
 
 def edit_help_button():
     # https://stackoverflow.com/questions/11076975/insert-text-into-textarea-at-cursor-position-javascript
-    '''<script>
-                function insertAtCursor(myField, myValue) {
-                    if (document.selection) { 
-                        document.getElementById(myField).focus();
-                        sel = document.selection.createRange(); 
-                        sel.text = myValue; 
-                    } else if (document.getElementById(myField).selectionStart || document.getElementById(myField).selectionStart == '0') { 
-                        var startPos = document.getElementById(myField).selectionStart; 
-                        var endPos = document.getElementById(myField).selectionEnd; 
-                        document.getElementById(myField).value = document.getElementById(myField).value.substring(0, startPos) + myValue + document.getElementById(myField).value.substring(endPos, document.getElementById(myField).value.length); 
-                    } else { 
-                        document.getElementById(myField).value += myValue;
-                    }
-                }
-            </script>
-        '''
-
-    '<a href="javascript:void(0);" onclick="insertAtCursor(\'content\', \'[[]]\');">(링크)</a> <a href="javascript:void(0);" onclick="insertAtCursor(\'content\', \'[macro()]\');">(매크로)</a> <a href="javascript:void(0);" onclick="insertAtCursor(\'content\', \'{{{#! }}}\');">(중괄호)</a><hr>'
+    '''
+    <script>
+        function insertAtCursor(myField, myValue) {
+            if (document.selection) { 
+                document.getElementById(myField).focus();
+
+                sel = document.selection.createRange();
+                sel.text = myValue; 
+            } else if (document.getElementById(myField).selectionStart || document.getElementById(myField).selectionStart == '0') {
+                var startPos = document.getElementById(myField).selectionStart;
+                var endPos = document.getElementById(myField).selectionEnd;
+
+                document.getElementById(myField).value = document.getElementById(myField).value.substring(0, startPos) + myValue + document.getElementById(myField).value.substring(endPos, document.getElementById(myField).value.length); 
+            } else {
+                document.getElementById(myField).value += myValue;
+            }
+        }
+    </script>
+    '''
+
+    insert_list = [['[[]]', '링크'], ['[()]', '매크로'], ['{{{#!}}}', '중괄호']]
+
+    '<a href="javascript:void(0);" onclick="insertAtCursor(\'content\', \'B\');">(A)</a>'
 
     return ['', '']
 
-def ip_warring(conn):
-    curs = conn.cursor()
-
-    if custom(conn)[2] == 0:    
+def ip_warring():
+    if custom()[2] == 0:    
         curs.execute('select data from other where name = "no_login_warring"')
         data = curs.fetchall()
         if data and data[0][0] != '':
             text_data = '<span>' + data[0][0] + '</span><hr>'
         else:
-            text_data = '<span>비 로그인 상태입니다. 비 로그인으로 진행 시 아이피가 기록됩니다.</span><hr>'
+            text_data = '<span>' + load_lang('no_login_warring') + '</span><hr>'
     else:
         text_data = ''
 
     return text_data
 
-def skin_check(conn):
-    curs = conn.cursor()
-
+def skin_check():
     skin = './views/acme/'
     
     try:
@@ -136,20 +151,18 @@ def next_fix(link, num, page, end = 50):
 
     if num == 1:
         if len(page) == end:
-            list_data += '<hr><a href="' + link + str(num + 1) + '">(이후)</a>'
+            list_data += '<hr><a href="' + link + str(num + 1) + '">(' + load_lang('next') + ')</a>'
     elif len(page) != end:
-        list_data += '<hr><a href="' + link + str(num - 1) + '">(이전)</a>'
+        list_data += '<hr><a href="' + link + str(num - 1) + '">(' + load_lang('previous') + ')</a>'
     else:
-        list_data += '<hr><a href="' + link + str(num - 1) + '">(이전)</a> <a href="' + link + str(num + 1) + '">(이후)</a>'
+        list_data += '<hr><a href="' + link + str(num - 1) + '">(' + load_lang('previous') + ')</a> <a href="' + link + str(num + 1) + '">(' + load_lang('next') + ')</a>'
 
     return list_data
 
 def other2(origin):
-    return origin + ['제거 되었음. 스킨 업데이트 필요함.']    
-
-def wiki_set(conn, num):
-    curs = conn.cursor()
+    return origin + ['Deleted']
 
+def wiki_set(num):
     if num == 1:
         data_list = []
 
@@ -216,11 +229,9 @@ def diff(seqm):
             
     return ''.join(output)
            
-def admin_check(conn, num, what):
+def admin_check(num, what):
     ip = ip_check() 
 
-    curs = conn.cursor()
-
     curs.execute("select acl from user where id = ?", [ip])
     user = curs.fetchall()
     if user:
@@ -257,18 +268,17 @@ def admin_check(conn, num, what):
                 else:
                     break
 
-def ip_pas(conn, raw_ip):
-    curs = conn.cursor()
+def ip_pas(raw_ip):
     hide = 0
 
     if re.search("(\.|:)", raw_ip):
-        if not re.search("^도구:", raw_ip):    
+        if not re.search("^" + load_lang('tool') + ":", raw_ip):    
             curs.execute("select data from other where name = 'ip_view'")
             data = curs.fetchall()
             if data and data[0][0] != '':
                 ip = '<span style="font-size: 75%;">' + hashlib.md5(bytes(raw_ip, 'utf-8')).hexdigest() + '</span>'
 
-                if not admin_check(conn, 'ban', None):
+                if not admin_check('ban', None):
                     hide = 1
             else:
                 ip = raw_ip
@@ -276,26 +286,24 @@ def ip_pas(conn, raw_ip):
             ip = raw_ip
             hide = 1
     else:
-        curs.execute("select title from data where title = ?", ['사용자:' + raw_ip])
+        curs.execute("select title from data where title = ?", [load_lang('user') + ':' + raw_ip])
         if curs.fetchall():
-            ip = '<a href="/w/' + url_pas('사용자:' + raw_ip) + '">' + raw_ip + '</a>'
+            ip = '<a href="/w/' + url_pas(load_lang('user') + ':' + raw_ip) + '">' + raw_ip + '</a>'
         else:
-            ip = '<a id="not_thing" href="/w/' + url_pas('사용자:' + raw_ip) + '">' + raw_ip + '</a>'
+            ip = '<a id="not_thing" href="/w/' + url_pas(load_lang('user') + ':' + raw_ip) + '">' + raw_ip + '</a>'
          
     if hide == 0:
-        ip += ' <a href="/record/' + url_pas(raw_ip) + '">(기록)</a>'
+        ip += ' <a href="/record/' + url_pas(raw_ip) + '">(' + load_lang('record') + ')</a>'
 
     return ip
 
-def custom(conn):
-    curs = conn.cursor()
-
-    if 'MyMaiToNight' in session:
-        user_head = session['MyMaiToNight']
+def custom():
+    if 'MyMaiToNight' in flask.session:
+        user_head = flask.session['MyMaiToNight']
     else:
         user_head = ''
 
-    if 'Now' in session and session['Now'] == 1:
+    if 'Now' in flask.session and flask.session['Now'] == 1:
         curs.execute('select name from alarm where name = ? limit 1', [ip_check()])
         if curs.fetchall():
             user_icon = 2
@@ -317,26 +325,24 @@ def custom(conn):
     if user_icon != 0:
         user_name = ip_check()
     else:
-        user_name = '사용자'
+        user_name = load_lang('user')
 
     return ['', '', user_icon, user_head, email, user_name]
 
-def acl_check(conn, name):
-    curs = conn.cursor()
-
+def acl_check(name):
     ip = ip_check()
 
-    if ban_check(conn) == 1:
+    if ban_check() == 1:
         return 1
 
-    acl_c = re.search("^사용자:([^/]*)", name)
+    acl_c = re.search("^" + load_lang('user') + ":([^/]*)", name)
     if acl_c:
         acl_n = acl_c.groups()
 
-        if admin_check(conn, 5, None) == 1:
+        if admin_check(5, None) == 1:
             return 0
 
-        curs.execute("select dec from acl where title = ?", ['사용자:' + acl_n[0]])
+        curs.execute("select dec from acl where title = ?", [load_lang('user') + ':' + acl_n[0]])
         acl_data = curs.fetchall()
         if acl_data:
             if acl_data[0][0] == 'all':
@@ -353,8 +359,8 @@ def acl_check(conn, name):
         else:
             return 1
 
-    file_c = re.search("^파일:(.*)", name)
-    if file_c and admin_check(conn, 5, 'edit (' + name + ')') != 1:
+    file_c = re.search("^" + load_lang('file') + ":(.*)", name)
+    if file_c and admin_check(5, 'edit (' + name + ')') != 1:
         return 1
 
     curs.execute("select acl from user where id = ?", [ip])
@@ -371,7 +377,7 @@ def acl_check(conn, name):
             if not user_data:
                 return 1
 
-            if not admin_check(conn, 5, 'edit (' + name + ')') == 1:
+            if not admin_check(5, 'edit (' + name + ')') == 1:
                 return 1
 
     curs.execute('select data from other where name = "edit"')
@@ -385,16 +391,14 @@ def acl_check(conn, name):
             if not user_data:
                 return 1
 
-            if not admin_check(conn, 5, None) == 1:
+            if not admin_check(5, None) == 1:
                 return 1
 
     return 0
 
-def ban_check(conn):
+def ban_check():
     ip = ip_check()
 
-    curs = conn.cursor()
-
     band = re.search("^([0-9]{1,3}\.[0-9]{1,3})", ip)
     if band:
         band_it = band.groups()[0]
@@ -411,12 +415,10 @@ def ban_check(conn):
     
     return 0
         
-def topic_check(conn, name, sub):
+def topic_check(name, sub):
     ip = ip_check()
 
-    curs = conn.cursor()
-
-    if ban_check(conn) == 1:
+    if ban_check() == 1:
         return 1
         
     curs.execute("select acl from user where id = ?", [ip])
@@ -433,19 +435,17 @@ def topic_check(conn, name, sub):
             if not user_data:
                 return 1
 
-            if not admin_check(conn, 3, 'topic (' + name + ')') == 1:
+            if not admin_check(3, 'topic (' + name + ')') == 1:
                 return 1
         
     curs.execute("select title from stop where title = ? and sub = ?", [name, sub])
     if curs.fetchall():
-        if not admin_check(conn, 3, 'topic (' + name + ')') == 1:
+        if not admin_check(3, 'topic (' + name + ')') == 1:
             return 1
 
     return 0
 
-def ban_insert(conn, name, end, why, login, blocker):
-    curs = conn.cursor()
-
+def ban_insert(name, end, why, login, blocker):
     time = get_time()
 
     if re.search("^([0-9]{1,3}\.[0-9]{1,3})$", name):
@@ -455,7 +455,7 @@ def ban_insert(conn, name, end, why, login, blocker):
 
     curs.execute("select block from ban where block = ?", [name])
     if curs.fetchall():
-        curs.execute("insert into rb (block, end, today, blocker, why, band) values (?, ?, ?, ?, ?, ?)", [name, '해제', time, blocker, '', band])
+        curs.execute("insert into rb (block, end, today, blocker, why, band) values (?, ?, ?, ?, ?, ?)", [name, load_lang('release'), time, blocker, '', band])
         curs.execute("delete from ban where block = ?", [name])
     else:
         if login != '':
@@ -471,24 +471,20 @@ def ban_insert(conn, name, end, why, login, blocker):
     
     conn.commit()
 
-def rd_plus(conn, title, sub, date):
-    curs = conn.cursor()
-
+def rd_plus(title, sub, date):
     curs.execute("select title from rd where title = ? and sub = ?", [title, sub])
     if curs.fetchall():
         curs.execute("update rd set date = ? where title = ? and sub = ?", [date, title, sub])
     else:
         curs.execute("insert into rd (title, sub, date) values (?, ?, ?)", [title, sub, date])
 
-def history_plus(conn, title, data, date, ip, send, leng):
-    curs = conn.cursor()
-
+def history_plus(title, data, date, ip, send, leng):
     curs.execute("select id from history where title = ? order by id + 0 desc limit 1", [title])
     id_data = curs.fetchall()
     if id_data:
         curs.execute("insert into history (id, title, data, date, ip, send, leng) values (?, ?, ?, ?, ?, ?, ?)", [str(int(id_data[0][0]) + 1), title, data, date, ip, send, leng])
     else:
-        curs.execute("insert into history (id, title, data, date, ip, send, leng) values ('1', ?, ?, ?, ?, ?, ?)", [title, data, date, ip, send + ' (새 문서)', leng])
+        curs.execute("insert into history (id, title, data, date, ip, send, leng) values ('1', ?, ?, ?, ?, ?, ?)", [title, data, date, ip, send + ' (' + load_lang('new') + ' ' + load_lang('document') + ')', leng])
 
 def leng_check(first, second):
     if first < second:
@@ -503,15 +499,13 @@ def leng_check(first, second):
 def redirect(data):
     return '<meta http-equiv="refresh" content="0; url=' + data + '">'
 
-def re_error(conn, data):
-    curs = conn.cursor()
-
+def re_error(data):
     if data == '/ban':
         ip = ip_check()
 
-        end = '<li>사유 : 권한이 맞지 않는 상태 입니다.</li>'
+        end = '<li>' + load_lang('why') + ' : ' + load_lang('authority_error') + '</li>'
 
-        if ban_check(conn) == 1:
+        if ban_check() == 1:
             curs.execute("select end, why from ban where block = ?", [ip])
             end_data = curs.fetchall()
             if not end_data:
@@ -521,7 +515,7 @@ def re_error(conn, data):
                     end_data = curs.fetchall()
             
             if end_data:
-                end = '<li>상태 : '
+                end = '<li>' + load_lang('state') + ' : '
 
                 if end_data[0][0]:
                     now = int(re.sub('(\-| |:)', '', get_time()))
@@ -531,20 +525,20 @@ def re_error(conn, data):
                         curs.execute("delete from ban where block = ?", [ip])
                         conn.commit()
 
-                        end += '차단이 풀렸습니다. 다시 해보세요.'
+                        end += 'Re Try.'
                     else:
-                        end += '차단 중 : ' + end_data[0][0]
+                        end += load_lang('why') + ' : ' + end_data[0][0]
                 else:
-                    end += '무기한 차단 상태 입니다.'
+                    end += load_lang('why') + ' : ' + load_lang('limitless')
                 
                 end += '</li>'
 
                 if end_data[0][1] != '':
-                    end += '<li>사유 : ' + end_data[0][1] + '</li>'
+                    end += '<li>' + load_lang('why') + ' : ' + end_data[0][1] + '</li>'
 
-        return html_minify(render_template(skin_check(conn), 
-            imp = ['권한 오류', wiki_set(conn, 1), custom(conn), other2([0, 0])],
-            data = '<h2>권한 상태</h2><ul>' + end + '</ul>',
+        return css_html_js_minify.html_minify(flask.render_template(skin_check(), 
+            imp = ['Error', wiki_set(1), custom(), other2([0, 0])],
+            data = '<h2>Error</h2><ul>' + end + '</ul>',
             menu = 0
         ))
 
@@ -552,76 +546,48 @@ def re_error(conn, data):
     if error_data:
         num = int(error_data.groups()[0])
         if num == 1:
-            title = '권한 오류'
-            data = '비 로그인 상태 입니다.'
+            data = load_lang('no_login_error')
         elif num == 2:
-            title = '권한 오류'
-            data = '이 계정이 없습니다.'
+            data = load_lang('no_exist_user_error')
         elif num == 3:
-            title = '권한 오류'
-            data = '권한이 모자랍니다.'
+            data = load_lang('authority_error')
         elif num == 4:
-            title = '권한 오류'
-            data = '관리자는 차단, 검사 할 수 없습니다.'
-        elif num == 5:
-            title = '사용자 오류'
-            data = '그런 계정이 없습니다.'
+            data = load_lang('no_admin_block_error')
         elif num == 6:
-            title = '가입 오류'
-            data = '동일한 아이디의 사용자가 있습니다.'
+            data = load_lang('same_id_exist_error')
         elif num == 7:
-            title = '가입 오류'
-            data = '아이디는 20글자보다 짧아야 합니다.'
+            data = load_lang('long_id_error')
         elif num == 8:
-            title = '가입 오류'
-            data = '아이디에는 한글과 알파벳과 공백만 허용 됩니다.'
+            data = load_lang('id_char_error')
         elif num == 9:
-            title = '파일 올리기 오류'
-            data = '파일이 없습니다.'
+            data = load_lang('file_exist_error')
         elif num == 10:
-            title = '변경 오류'
-            data = '비밀번호가 다릅니다.'
-        elif num == 11:
-            title = '로그인 오류'
-            data = '이미 로그인 되어 있습니다.'
+            data = load_lang('password_error')
         elif num == 13:
-            title = '리캡차 오류'
-            data = '리캡차를 통과하세요.'
+            data = load_lang('recaptcha_error')
         elif num == 14:
-            title = '파일 올리기 오류'
-            data = 'jpg, gif, jpeg, png, webp만 가능 합니다.'
+            data = load_lang('file_extension_error')
         elif num == 15:
-            title = '편집 오류'
-            data = '편집 기록은 500자를 넘을 수 없습니다.'
+            data = load_lang('edit_record_error')
         elif num == 16:
-            title = '파일 올리기 오류'
-            data = '동일한 이름의 파일이 있습니다.'
+            data = load_lang('same_file_error')
         elif num == 17:
-            title = '파일 올리기 오류'
-            data = '파일 용량은 ' + wiki_set(conn, 3) + 'MB를 넘길 수 없습니다.'
-        elif num == 18:
-            title = '편집 오류'
-            data = '내용이 원래 문서와 동일 합니다.'
+            data = load_lang('file_capacity_error') + ' ' + wiki_set(3)
         elif num == 19:
-            title = '이동 오류'
-            data = '이동 하려는 곳에 문서가 이미 있습니다.'
+            data = load_lang('decument_exist_error')
         elif num == 20:
-            title = '비밀번호 오류'
-            data = '재 확인이랑 비밀번호가 다릅니다.'
+            data = load_lang('password_diffrent_error')
         elif num == 21:
-            title = '편집 오류'
-            data = '편집 필터에 의해 검열 되었습니다.'
+            data = load_lang('edit_filter_error')
         elif num == 22:
-            title = '파일 올리기 오류'
-            data = '파일 이름은 알파벳, 한글, 띄어쓰기, 언더바, 빼기표만 허용 됩니다.'
+            data = load_lang('file_name_error')
         else:
-            title = '정체 불명의 오류'
             data = '???'
 
         if title:
-            return html_minify(render_template(skin_check(conn), 
-                imp = [title, wiki_set(conn, 1), custom(conn), other2([0, 0])],
-                data = '<h2>오류 발생</h2><ul><li>' + data + '</li></ul>',
+            return css_html_js_minify.html_minify(flask.render_template(skin_check(), 
+                imp = ['Error', wiki_set(1), custom(), other2([0, 0])],
+                data = '<h2>Error</h2><ul><li>' + data + '</li></ul>',
                 menu = 0
             ))
         else:

+ 0 - 42
language/en-UK.json

@@ -1,42 +0,0 @@
-{
-    "edit" : "Edit",
-    "history" : "History",
-    "delete" : "Delete",
-    "bulk_delete" : "Bulk delete",
-    "edit_filter" : "Edit filtering",
-    "move" : "Move",
-    "hide" : "Hide",
-    "list" : "List",
-    "revert" : "Undo",
-    "version" : "Version",
-    "document" : "Documents",
-    "all" : "Everything",
-    "ban" : "Ban",
-    "release" : "Pardon",
-    "save" : "Save",
-    "other" : "Others",
-    "tool" : "Tools",
-    "plus" : "Add",
-    "user" : "User",
-    "alarm" : "Alarm",
-    "watchlist" : "Watching list",
-    "my_info" : "About me",
-    "recent" : "Recently",
-    "recent_changes" : "Recently editted",
-    "discussion" : "Discussions",
-    "login" : "Sign in",
-    "logout" : "Sign out",
-    "register" : "Sign up",
-    "no_alarm" : "There is no available alarm",
-    "able" : "Able to",
-    "year" : "Year",
-    "month" : "Month",
-    "day" : "Day",
-    "normal" : "Common",
-    "subscriber" : "Member",
-    "admin" : "Administrator",
-    "owner" : "Owner",
-    "admin_group" : "Moderation group",
-    "user_css_warring" : "User's CSS will deleted if you close the browser or when you are editting as guest",
-    "http_warring" : "Warning : If you are not on HTTPS connection, your information can be leaked. We won't response to that."
-}

+ 54 - 11
language/en-US.json

@@ -8,7 +8,8 @@
     "hide" : "Hide",
     "list" : "List",
     "revert" : "Undo",
-    "version" : "Version",
+    "version" : " Ver",
+    "new" : "New",
     "document" : "Documents",
     "all" : "Everything",
     "ban" : "Ban",
@@ -31,30 +32,72 @@
     "editor" : "Editor",
     "hour" : "Hour",
     "time" : "Time",
+    "close" : "Close",
+    "stop" : "Stop",
+    "restart" : "Restart",
+    "agreement" : "Agreement",
     "backlink" : "Back Link",
     "why" : "Why",
+    "random" : "Random",
     "authority" : "Authority",
-    
+    "file" : "File",
+    "change" : "Changes",
+    "compare" : "Compare",
+    "count" : "Count",
+    "check" : "Check",
     "user" : "User",
     "alarm" : "Alarm",
+    "preview" : "Preview",
     "watchlist" : "Watching List",
     "my_info" : "About Me",
-    
-    "recent" : "Recently",
-    "recent_changes" : "Recent Changes",
-    
-    "discussion" : "Discussions",
-
+    "state" : "State",
+    "recent" : "Recent",
+    "discussion" : "Debates",
     "login" : "Login",
     "logout" : "Logout",
     "register" : "Register",
     "no_alarm" : "There is no alram available",
-
+    "able" : "Able",
+    "year" : "Year",
+    "month" : "Month",
+    "day" : "Day",
     "normal" : "Normal",
     "subscriber" : "User",
     "admin" : "Admin",
+    "next" : "Next",
+    "previous" : "Previous",
     "owner" : "Owner",
-    "admin_group" : "MOD group",
+    "admin_group" : "MOD Group",
+    "limitless" : "Limitless",
+    "period" : "Period",
+    "now" : "Now",
+    "blocked" : "Blocked",
+    "band" : "Band",
+    "notice" : "Notice",
+    "writer" : "Writer",
+    "upper" : "Upper",
+    "under" : "Under",
+    "pass" : "Pass",
+    "category" : "Category",
     "user_css_warring" : "User's CSS will deleted if you close the browser or when you are editting as guest",
-    "http_warring" : "Warning : If you are not on HTTPS connection, your information can be leaked. We won't response to that."
+    "http_warring" : "Warning : If you are not on HTTPS connection, your information can be leaked. We won't response to that.",
+    "no_login_warring" : "Non-login status. IP is logged when working with non-login.",
+    "authority_error" : "Insufficient privileges.",
+    "no_login_error" : "Non-login status.",
+    "no_exist_user_error" : "Account does not exist.",
+    "no_admin_block_error" : "Administrators can not block, check.",
+    "same_id_exist_error" : "There are users with the same ID.",
+    "long_id_error" : "ID must be shorter than 20 characters.",
+    "id_char_error" : "Only hangul, alphabet and space are allowed for ID.",
+    "file_exist_error" : "File does not exist.",
+    "password_error" : "The password is different.",
+    "recaptcha_error" : "Go through the reCAPTCHA.",
+    "file_extension_error" : "Only jpg, gif, jpeg, png, webp is possible.",
+    "edit_record_error" : "Edit record can not be more than 500 characters.",
+    "same_file_error" : "A file with the same name exists.",
+    "file_capacity_error" : "Maximum file capacity (MB) :",
+    "decument_exist_error" : "The document already exists where you want to move it.",
+    "password_diffrent_error" : "Reconfirm password and input password are different.",
+    "edit_filter_error" : "Censored by edit filter.",
+    "file_name_error" : "Only alphabet, hangul, space, underscore, and minus signs are allowed for file names."
 }

+ 102 - 50
language/ko-KR.json

@@ -1,52 +1,104 @@
 {
-    "edit" : "편집",
-    "history" : "역사",
-    "delete" : "삭제",
-    "bulk_delete" : "대량 삭제",
-    "edit_filter" : "편집 필터",
-    "move" : "이동",
-    "hide" : "숨김",
-    "list" : "목록",
-    "revert" : "되돌리기",
-    "version" : "판",
-    "document" : "문서",
-    "all" : "모든",
-    "ban" : "차단",
-    "release" : "해제",
-    "save" : "저장",
-    "other" : "기타",
-    "tool" : "도구",
-    "plus" : "추가",
-    "open" : "열린",
-    "search" : "검색",
-
-    "user" : "사용자",
-    "alarm" : "알림",
-    "watchlist" : "주시 문서",
-    "my_info" : "내 정보",
-    
-    "recent" : "최근",
-    "recent_changes" : "최근 변경",
-    
-    "discussion" : "토론",
-    
-    "login" : "로그인",
-    "logout" : "로그아웃",
-    "register" : "회원가입",
-    "no_alarm" : "알림이 없습니다.",
-    
-    "able" : "가능",
-    
-    "year" : "년",
-    "month" : "월",
-    "day" : "일",
-    
-    "normal" : "일반",
-    "subscriber" : "가입자",
-    "admin" : "관리자",
-    "owner" : "소유자",
-    "admin_group" : "관리 그룹",
-
-    "user_css_warring" : "비 로그인의 경우에는 사용자 CSS가 로그인하거나 브라우저 닫으면 날아갑니다.",
-    "http_warring" : "주의 : 만약 HTTPS 연결이 아닌 경우 데이터가 유출될 가능성이 있습니다. 이에 대해 책임지지 않습니다."
+    "edit": "편집",
+    "history": "역사",
+    "delete": "삭제",
+    "bulk_delete": "대량 삭제",
+    "edit_filter": "편집 필터",
+    "move": " 이동",
+    "hide": "숨김",
+    "list": "목록",
+    "revert": "되돌리기",
+    "version": "판",
+    "document": "문서",
+    "all": "모든",
+    "ban": "차단",
+    "release": "해제",
+    "save": "저장",
+    "other": "기타",
+    "tool": "도구",
+    "plus": "추가",
+    "open": "열린",
+    "search": "검색",
+    "user": "사용자",
+    "alarm": "알림",
+    "watchlist": "주시 문서",
+    "my_info": "내 정보",
+    "recent": "최근",
+    "recent_changes": "최근 변경",
+    "discussion": "토론",
+    "login": "로그인",
+    "logout": "로그아웃",
+    "register": "회원가입",
+    "no_alarm": " 알림이 없습니다.",
+    "able": "가능",
+    "year": "년",
+    "month": "월",
+    "day": "일",
+    "normal": "일반",
+    "subscriber": "가입자",
+    "admin": "관리자",
+    "owner": "소유자",
+    "admin_group": "관리 그룹",
+    "user_css_warring": "비 로그인의 경우에는 사용자 CSS가 로그인하거나 브라우저 닫으면 날아갑니다.",
+    "http_warring": "주의 : 만약 HTTPS 연결이 아닌 경우 데이터가 유출될 가능성이 있습니다. 이에 대해 책임지지 않습니다.",
+    "new": "새",
+    "need": "필요한",
+    "upload": "파일 올리기",
+    "record": "기록",
+    "name": "이름",
+    "license": "라이선스",
+    "interwiki": "인터위키",
+    "update": "업데이트",
+    "setting": "설정",
+    "create": "생성",
+    "editor": "수정자",
+    "hour": "시간",
+    "time": "시각",
+    "close": "닫기",
+    "stop": "정지",
+    "restart": "재시작",
+    "agreement": "합의",
+    "backlink": "역링크",
+    "why": "사유",
+    "random": "무작위",
+    "authority": "권한",
+    "file": "파일",
+    "change": "변경",
+    "compare": "비교",
+    "count": "횟수",
+    "check": "검사",
+    "preview": "미리보기",
+    "next": "다음",
+    "previous": "이전",
+    "no_login_warring": "비 로그인 상태로 진행 시 IP가 기록될 수 있습니다.",
+    "state": "상태",
+    "limitless": "무기한",
+    "period": "기간",
+    "now": "현재",
+    "blocked": "차단자",
+    "band": "대역",
+    "notice": "공지",
+    "writer": "작성자",
+    "upper": "상위",
+    "under": "하위",
+    "pass": "통과",
+    "category": "분류",
+    "authority_error": "권한이 부족합니다.",
+    "no_login_error": "비 로그인 상태 입니다.",
+    "no_exist_user_error": "계정이 없습니다.",
+    "no_admin_block_error": "관리자는 차단, 검사 할 수 없습니다.",
+    "same_id_exist_error": "동일한 아이디의 사용자가 있습니다.",
+    "long_id_error": "아이디는 20글자보다 짧아야 합니다.",
+    "id_char_error": "아이디에는 한글과 알파벳과 공백만 허용 됩니다.",
+    "file_exist_error": "파일이 없습니다.",
+    "password_error": "비밀번호가 다릅니다.",
+    "recaptcha_error": "리캡차를 통과하세요.",
+    "file_extension_error": "jpg, gif, jpeg, png, webp만 가능 합니다.",
+    "edit_record_error": "편집 기록은 500자를 넘을 수 없습니다.",
+    "same_file_error": "동일한 이름의 파일이 있습니다.",
+    "file_capacity_error": "파일 최대 용량 (MB) :",
+    "decument_exist_error": "내용이 원래 문서와 동일 합니다.",
+    "password_diffrent_error": "재 확인 비밀번호와 입력 비밀번호가 다릅니다.",
+    "edit_filter_error": "편집 필터에 의해 검열 되었습니다.",
+    "file_name_error": "파일 이름은 알파벳, 한글, 띄어쓰기, 언더바, 빼기표만 허용 됩니다."
 }

+ 18 - 0
language/test.py

@@ -0,0 +1,18 @@
+import json
+
+print('name : ', end = '')
+b = input()
+
+json_data = open('en-US.json', 'rt', encoding='utf-8').read()
+a_json = json.loads(json_data)
+
+json_data = open(str(b) + '.json', 'rt', encoding='utf-8').read()
+b_json = json.loads(json_data)
+
+for a_in in a_json:
+    if not a_in in b_json:
+        print(a_in + ' : ', end = '')
+        c = input()
+        b_json[a_in] = c
+        
+print(str(b_json).replace(', ', ',\n    ').replace('{', '{\n    ').replace('}', '\n}').replace('\'', '"'))

+ 20 - 6
mark.py

@@ -7,6 +7,13 @@ from urllib import parse
 import time
 import threading
 
+def load_conn2(data):
+    global conn
+    global curs
+
+    conn = data
+    curs = conn.cursor()
+
 def send_parser(data):
     data = html.escape(data)
     
@@ -17,20 +24,27 @@ def send_parser(data):
     
     return data
     
-def plusing(conn, name, link, backtype):
-    curs = conn.cursor()
+def plusing(name, link, backtype):
     curs.execute("select title from back where title = ? and link = ? and type = ?", [link, name, backtype])
     if not curs.fetchall():
         curs.execute("insert into back (title, link, type) values (?, ?, ?)", [link, name, backtype])
 
-def namumark(conn, title, data, num):
+def namumark(title, data, num):
     data = start(conn, data, title)
-    if num == 1:        
-        for back_data in data[2]:
-            thread_start = threading.Thread(target = plusing, args = [conn, back_data[0], back_data[1], back_data[2]])
+    if num == 1:
+        i = 0
+        while 1:
+            try:
+                _ = data[2][i][0]
+            except:
+                break
+
+            thread_start = threading.Thread(target = plusing, args = [data[2][i][0], data[2][i][1], data[2][i][2]])
             thread_start.start()
             thread_start.join()
 
+            i += 1
+
         conn.commit()
         
     return data[0] + data[1]

+ 100 - 41
set_mark/start.py

@@ -370,7 +370,7 @@ def start(conn, data, title):
                                                 else:
                                                     middle_data_2 = ['']
 
-                                                middle_list += ['div']
+                                                middle_list += ['div_end']
                                                 
                                                 data = re.sub('{{{#!wiki(?: style=(?:&quot;|&#x27;)((?:(?!&quot;|&#x27;).)*)(?:&quot;|&#x27;))?\n?', '<div id="wiki_div" style="' + str(middle_data_2[0]) + '">', data, 1)
                                             else:
@@ -481,7 +481,7 @@ def start(conn, data, title):
             else:
                 end_parser = wiki_table_data[1]
 
-            data = re.sub('<div id="wiki_div" ((?:(?!>).)+)>((?:(?!<div id="wiki_div"|<\/div_end>).\n*)+)<\/div_end>', '<div ' + wiki_table_data[0] + '>' + end_parser + '</div_end>', data, 1)
+            data = re.sub('<div id="wiki_div" ((?:(?!>).)+)>((?:(?!<div id="wiki_div"|<\/div_end>).\n*)+)<\/div_end>', '<div ' + wiki_table_data[0] + '>' + end_parser + '</div>', data, 1)
         else:
             break
             
@@ -494,7 +494,13 @@ def start(conn, data, title):
         math = re.search('&lt;math&gt;((?:(?!&lt;\/math&gt;).)+)&lt;\/math&gt;', data)
         if math:
             if first == 0:
-                plus_data += '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0/katex.min.css" integrity="sha384-TEMocfGvRuD1rIAacqrknm5BQZ7W7uWitoih+jMNFXQIbNl16bO8OZmylH/Vi/Ei" crossorigin="anonymous"><script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0/katex.min.js" integrity="sha384-jmxIlussZWB7qCuB+PgKG1uLjjxbVVIayPJwi6cG6Zb4YKq0JIw+OMnkkEC7kYCq" crossorigin="anonymous"></script>'
+                plus_data += '''
+                            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0/katex.min.css"
+                                integrity="sha384-TEMocfGvRuD1rIAacqrknm5BQZ7W7uWitoih+jMNFXQIbNl16bO8OZmylH/Vi/Ei"
+                                crossorigin="anonymous">
+                            <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0/katex.min.js" 
+                                integrity="sha384-jmxIlussZWB7qCuB+PgKG1uLjjxbVVIayPJwi6cG6Zb4YKq0JIw+OMnkkEC7kYCq"
+                                crossorigin="anonymous"></script>'''
 
             math = math.groups()[0]
             
@@ -620,28 +626,38 @@ def start(conn, data, title):
     time_data = re.search('^([0-9]{4}-[0-9]{2}-[0-9]{2})', now_time)
     time = time_data.groups()
     
-    age_data = re.findall('\[age\(([0-9]{4}-[0-9]{2}-[0-9]{2})\)\]', data)
-    for age in age_data:
-        old = datetime.datetime.strptime(time[0], '%Y-%m-%d')
-        will = datetime.datetime.strptime(age, '%Y-%m-%d')
-        
-        e_data = old - will
-        
-        data = re.sub('\[age\(([0-9]{4})-([0-9]{2})-([0-9]{2})\)\]', str(int(int(e_data.days) / 365)), data, 1)
+    while 1:
+        age_data = re.search('\[age\(([0-9]{4}-[0-9]{2}-[0-9]{2})\)\]', data)
+        if age_data:
+            age = age_data.groups[0]
 
-    dday_data = re.findall('\[dday\(([0-9]{4}-[0-9]{2}-[0-9]{2})\)\]', data)
-    for dday in dday_data:
-        old = datetime.datetime.strptime(time[0], '%Y-%m-%d')
-        will = datetime.datetime.strptime(dday, '%Y-%m-%d')
-        
-        e_data = old - will
-        
-        if re.search('^-', str(e_data.days)):
-            e_day = str(e_data.days)
+            old = datetime.datetime.strptime(time[0], '%Y-%m-%d')
+            will = datetime.datetime.strptime(age, '%Y-%m-%d')
+            
+            e_data = old - will
+            
+            data = re.sub('\[age\(([0-9]{4})-([0-9]{2})-([0-9]{2})\)\]', str(int(e_data.days / 365)), data, 1)
         else:
-            e_day = '+' + str(e_data.days)
+            break
 
-        data = re.sub('\[dday\(([0-9]{4}-[0-9]{2}-[0-9]{2})\)\]', e_day, data, 1)
+    while 1:
+        dday_data = re.search('\[dday\(([0-9]{4}-[0-9]{2}-[0-9]{2})\)\]', data)
+        if dday_data:
+            dday = dday_data.groups[0]
+
+            old = datetime.datetime.strptime(time[0], '%Y-%m-%d')
+            will = datetime.datetime.strptime(dday, '%Y-%m-%d')
+            
+            e_data = old - will
+            
+            if re.search('^-', str(e_data.days)):
+                e_day = str(e_data.days)
+            else:
+                e_day = '+' + str(e_data.days)
+
+            data = re.sub('\[dday\(([0-9]{4}-[0-9]{2}-[0-9]{2})\)\]', e_day, data, 1)
+        else:
+            break
 
     # 유튜브, 카카오 티비 처리
     while 1:
@@ -908,32 +924,58 @@ def start(conn, data, title):
             
     # 각주 처리
     footnote_number = 0
-    footnote_all = '\n<hr><ul id="footnote_data">'
+    footnote_all = []
     footnote_dict = {}
+    footnote_re = {}
+    footdata_all = '\n<hr><ul id="footnote_data">'
     while 1:
         footnote = re.search('(?:\[\*((?:(?! |\]).)*)(?: ((?:(?!\]).)+))?\]|(\[각주\]))', data)
         if footnote:
             footnote_data = footnote.groups()
             if footnote_data[2]:
-                footnote_all += '</ul>'
+                footnote_all.sort()
+                
+                for footdata in footnote_all:
+                    if footdata[2] == 0:
+                        footdata_in = ''
+                    else:
+                        footdata_in = footdata[2]
+
+                    footdata_all += '<li><a href="#rfn-' + str(footdata[0]) + '" id="fn-' + str(footdata[0]) + '">(' + footdata[1] + ')</a> ' + footdata_in + '</li>'
                 
-                data = re.sub('(?:\[\*((?:(?! ).)*) ((?:(?!\]).)+)\]|(\[각주\]))', footnote_all, data, 1)
+                data = re.sub('(?:\[\*((?:(?! ).)*) ((?:(?!\]).)+)\]|(\[각주\]))', footdata_all + '</ul>', data, 1)
                 
-                footnote_all = '\n<hr><ul id="footnote_data">'
+                footnote_all = []
+                footdata_all = '\n<hr><ul id="footnote_data">'
             else:
                 footnote = footnote_data[1]
                 footnote_name = footnote_data[0]
                 if footnote_name and not footnote:
-                    data = re.sub('(?:\[\*((?:(?! |\]).)*)(?: ((?:(?!\]).)+))?\]|(\[각주\]))', '<sup><a href="#fn-' + footnote_dict[footnote_name] + '" id="rfn-' + footnote_dict[footnote_name] + '">(' + footnote_name + ')</a></sup>', data, 1)
+                    if footnote_name in footnote_dict:
+                        footnote_re[footnote_name] += 1
+
+                        foot_plus_num = str(footnote_re[footnote_name])
+                        footshort = footnote_dict[footnote_name] + '.' + foot_plus_num
+
+                        footnote_all += [[float(footshort), footshort, 0]]
+
+                        data = re.sub('(?:\[\*((?:(?! |\]).)*)(?: ((?:(?!\]).)+))?\]|(\[각주\]))', '<sup><a href="#fn-' + footshort + '" id="rfn-' + footshort + '">(' + footshort + ')</a></sup>', data, 1)
+                    else:
+                        data = re.sub('(?:\[\*((?:(?! |\]).)*)(?: ((?:(?!\]).)+))?\]|(\[각주\]))', '<sup><a href="#">(' + footnote_name + ')</a></sup>', data, 1)
                 else:
                     footnote_number += 1
 
                     if not footnote_name:
                         footnote_name = str(footnote_number)
+
+                    footnote_dict.update({ footnote_name : str(footnote_number) })
+
+                    if not footnote_name in footnote_re:
+                        footnote_re.update({ footnote_name : 0 })
                     else:
-                        footnote_dict.update({ footnote_name : str(footnote_number) })
+                        footnote_re[footnote_name] += 1
 
-                    footnote_all += '<li><a href="#rfn-' + str(footnote_number) + '" id="fn-' + str(footnote_number) + '">(' + footnote_name + ')</a> ' + footnote + '</li>'
+                    footnote_all += [[footnote_number, footnote_name, footnote]]
                     
                     data = re.sub('(?:\[\*((?:(?! |\]).)*)(?: ((?:(?!\]).)+))?\]|(\[각주\]))', '<sup><a href="#fn-' + str(footnote_number) + '" id="rfn-' + str(footnote_number) + '">(' + footnote_name + ')</a></sup>', data, 1)
         else:
@@ -941,12 +983,21 @@ def start(conn, data, title):
 
     data = re.sub('\n+$', '', data)
 
-    footnote_all += '</ul>'
-    if footnote_all == '\n<hr><ul id="footnote_data"></ul>':
-        footnote_all = ''
+    footnote_all.sort()
 
-    
-    data = re.sub('\n$', footnote_all, data + '\n', 1)
+    for footdata in footnote_all:
+        if footdata[2] == 0:
+            footdata_in = ''
+        else:
+            footdata_in = footdata[2]
+
+        footdata_all += '<li><a href="#rfn-' + str(footdata[0]) + '" id="fn-' + str(footdata[0]) + '">(' + footdata[1] + ')</a> ' + footdata_in + '</li>'
+
+    footdata_all += '</ul>'
+    if footdata_all == '\n<hr><ul id="footnote_data"></ul>':
+        footdata_all = ''
+
+    data = re.sub('\n$', footdata_all, data + '\n', 1)
 
     # 분류 마지막 처리
     category += '</div>'
@@ -958,15 +1009,23 @@ def start(conn, data, title):
     data += category
     
     # NoWiki 마지막 처리
-    for re_data in end_data:
-        if re_data[2] == 'normal':
-            data = data.replace('<span id="' + re_data[0] + '"></span>', re_data[1])
-            data = data.replace(tool.url_pas('<span id="' + re_data[0] + '"></span>'), tool.url_pas(re_data[1]))
+    i = 0
+    while 1:
+        try:
+            _ = end_data[i][0]
+        except:
+            break
+
+        if end_data[i][2] == 'normal':
+            data = data.replace('<span id="' + end_data[i][0] + '"></span>', end_data[i][1])
+            data = data.replace(tool.url_pas('<span id="' + end_data[i][0] + '"></span>'), tool.url_pas(end_data[i][1]))
         else:
-            if re.search('\n', re_data[1]):
-                data = data.replace('<span id="' + re_data[0] + '"></span>', '\n<pre>' + re.sub('^\n', '', re_data[1]) + '</pre>')
+            if re.search('\n', end_data[i][1]):
+                data = data.replace('<span id="' + end_data[i][0] + '"></span>', '\n<pre>' + re.sub('^\n', '', end_data[i][1]) + '</pre>')
             else:
-                data = data.replace('<span id="' + re_data[0] + '"></span>', '<code>' + re_data[1] + '</code>')
+                data = data.replace('<span id="' + end_data[i][0] + '"></span>', '<code>' + end_data[i][1] + '</code>')
+
+        i += 1
     
     # 마지막 처리
     data = re.sub('<\/td_end>', '</td>', data)

+ 11 - 13
set_mark/tool.py

@@ -1,23 +1,18 @@
-from flask import session, request
-
-from urllib import parse
-import time
+import flask
+import urllib.parse
 import datetime
 import re
 import hashlib
 
 def get_time():
-    now = time.localtime()
-    date = "%04d-%02d-%02d %02d:%02d:%02d" % (now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec)
-
-    return date
+    return str(datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S"))
     
 def ip_check():
-    if session and ('Now' and 'DREAMER') in session and session['Now'] == 1:
-        ip = session['DREAMER']
+    if flask.session and ('Now' and 'DREAMER') in flask.session and flask.session['Now'] == 1:
+        ip = flask.session['DREAMER']
     else:
         try:
-            ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr)
+            ip = flask.request.environ.get('HTTP_X_REAL_IP', flask.request.remote_addr)
         except:
             ip = 'None'
 
@@ -36,7 +31,10 @@ def savemark(data):
     return data
 
 def url_pas(data):
-    return parse.quote(data).replace('/','%2F')
+    return urllib.parse.quote(data).replace('/','%2F')
 
 def sha224(data):
-    return hashlib.sha224(bytes(data, 'utf-8')).hexdigest()
+    return hashlib.sha224(bytes(data, 'utf-8')).hexdigest()
+
+def md5_replace(data):
+    return hashlib.md5(data.encode()).hexdigest()

+ 22 - 13
views/acme/index.html

@@ -38,35 +38,44 @@
                                 <input style="display: inline-block;" class="form-control search" type="search" name="search" placeholder="Search" id="searchInput" autocomplete="off">
                             </form>                
                         </li>
-                        <li>
-                            <a href="/recent_changes">
-                                <i class="fa fa-refresh" aria-hidden="true"></i>
-                                최근 변경
-                            </a>
-                        </li>
-                        <li>
-                            <a href="/recent_discuss">
-                                <i class="fa fa-comment" aria-hidden="true"></i>
-                                최근 토론
+                        <li class="dropdown">
+                            <a class="dropdown-toggle" data-close-others="false" data-delay="0" data-hover="dropdown" data-toggle="dropdown" href="javascript:void(0);">
+                                <i class="fa fa-plus-circle" aria-hidden="true"></i>
+                                {{'recent'|load_lang}}
+                                <i class="fa fa-angle-down"></i>
                             </a>
+                            <ul role="menu" class="dropdown-menu">
+                                <li>
+                                    <a href="/recent_changes">
+                                        <i class="fa fa-refresh" aria-hidden="true"></i>
+                                        {{'change'|load_lang}}
+                                    </a>
+                                </li>
+                                <li>
+                                    <a href="/recent_discuss">
+                                        <i class="fa fa-comment" aria-hidden="true"></i>
+                                        {{'discussion'|load_lang}}
+                                    </a>
+                                </li>
+                            </ul>
                         </li>
                         <li class="dropdown">
                             <a class="dropdown-toggle" data-close-others="false" data-delay="0" data-hover="dropdown" data-toggle="dropdown" href="javascript:void(0);">
                                 <i class="fa fa-plus-circle" aria-hidden="true"></i>
-                                나머지
+                                {{'other'|load_lang}}
                                 <i class="fa fa-angle-down"></i>
                             </a>
                             <ul role="menu" class="dropdown-menu">
                                 <li>
                                     <a href="/random">
                                         <i class="fa fa-random" aria-hidden="true"></i>
-                                        무작위
+                                        {{'random'|load_lang}}
                                     </a>
                                 </li>
                                 <li>
                                     <a href="/other">
                                         <i class="fa fa-cogs" aria-hidden="true"></i>
-                                        기타
+                                        {{'tool'|load_lang}}
                                     </a>
                                 </li>
                             </ul>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio