# HG changeset patch # User Dan # Date 1193604024 14400 # Node ID 2b283402e4e403ecdce61b274bb67de907467b84 # Parent 8a00247d1deea4f9395e41bcdad2cbbf3b14f04d Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface. diff -r 8a00247d1dee -r 2b283402e4e4 includes/clientside/static/enano-lib-basic.js --- a/includes/clientside/static/enano-lib-basic.js Sun Oct 28 14:32:13 2007 -0400 +++ b/includes/clientside/static/enano-lib-basic.js Sun Oct 28 16:40:24 2007 -0400 @@ -277,6 +277,7 @@ 'toolbar.js', 'windows.js', 'rijndael.js', + 'l10n.js', 'template-compiler.js', 'acl.js', 'comments.js', diff -r 8a00247d1dee -r 2b283402e4e4 includes/clientside/static/faders.js --- a/includes/clientside/static/faders.js Sun Oct 28 14:32:13 2007 -0400 +++ b/includes/clientside/static/faders.js Sun Oct 28 16:40:24 2007 -0400 @@ -441,7 +441,7 @@ function mb_logout() { - var mb = new messagebox(MB_YESNO|MB_ICONQUESTION, 'Are you sure you want to log out?', 'If you log out, you will no longer be able to access your user preferences, your private messages, or certain areas of this site until you log in again.'); + var mb = new messagebox(MB_YESNO|MB_ICONQUESTION, $lang.get('user_logout_confirm_title'), $lang.get('user_logout_confirm_body')); mb.onclick['Yes'] = function() { window.location = makeUrlNS('Special', 'Logout/' + title); diff -r 8a00247d1dee -r 2b283402e4e4 includes/clientside/static/l10n.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/includes/clientside/static/l10n.js Sun Oct 28 16:40:24 2007 -0400 @@ -0,0 +1,60 @@ +/* + * Enano client-side localization library + */ + +var Language = function(lang_id) +{ + if ( typeof(enano_lang) != 'object' ) + return false; + if ( typeof(enano_lang[lang_id]) != 'object' ) + return false; + this.strings = enano_lang[lang_id]; + + this.get = function(string_id, subst) + { + var catname = string_id.substr(0, string_id.indexOf('_')); + var string_name = string_id.substr(string_id.indexOf('_') + 1); + if ( typeof(this.strings[catname]) != 'object' ) + return string_id; + if ( typeof(this.strings[catname][string_name]) != 'string' ) + return string_id; + return '[LJS] ' + this.perform_subst(this.strings[catname][string_name], subst); + } + + this.perform_subst = function(str, subst) + { + // var this_regex = /%this\.([a-z0-9_]+)%/; + // var match; + // while ( str.match(this_regex) ) + // { + // match = str.match(this_regex); + // alert(match); + // } + // hackish workaround for %config.*% + str = str.replace(/%config\.([a-z0-9_]+)%/g, '%$1%'); + if ( typeof(subst) == 'object' ) + { + for ( var i in subst ) + { + if ( !i.match(/^([a-z0-9_]+)$/) ) + continue; + var regex = new RegExp('%' + i + '%', 'g'); + str = str.replace(regex, subst[i]); + } + } + return str; + } + +} + +var $lang; + +var language_onload = function() +{ + $lang = new Language(ENANO_LANG_ID); + // for debugging :-) + // alert( $lang.get('user_err_invalid_credentials_lockout_captcha', { lockout_fails: '3', lockout_threshold: '5', lockout_duration: '15' }) ); +} + +addOnloadHook(language_onload); + diff -r 8a00247d1dee -r 2b283402e4e4 includes/clientside/static/misc.js --- a/includes/clientside/static/misc.js Sun Oct 28 14:32:13 2007 -0400 +++ b/includes/clientside/static/misc.js Sun Oct 28 16:40:24 2007 -0400 @@ -311,38 +311,49 @@ switch($data.error) { case 'key_not_found': - $errstring = 'Enano couldn\'t look up the encryption key used to encrypt your password. This most often happens if a cache rotation occurred during your login attempt, or if you refreshed the login page.'; + $errstring = $lang.get('user_err_key_not_found'); break; case 'key_wrong_length': - $errstring = 'The encryption key was the wrong length.'; + $errstring = $lang.get('user_err_key_wrong_length'); break; case 'too_big_for_britches': - $errstring = 'You are trying to authenticate at a level that your user account does not permit.'; + $errstring = $lang.get('user_err_too_big_for_britches'); break; case 'invalid_credentials': - $errstring = 'You have entered an invalid username or password. Please enter your login details again.'; + $errstring = $lang.get('user_err_invalid_credentials'); + var subst = { + lockout_fails: $data.lockout_fails, + lockout_threshold: $data.lockout_threshold, + lockout_duration: $data.lockout_duration + } if ( $data.lockout_policy == 'lockout' ) { - $errstring += ' You have used up '+$data['lockout_fails']+' out of '+$data['lockout_threshold']+' login attempts. After you have used up all '+$data['lockout_threshold']+' login attempts, you will be locked out from logging in for '+$data['lockout_duration']+' minutes.'; + $errstring += $lang.get('user_err_invalid_credentials_lockout', subst); } else if ( $data.lockout_policy == 'captcha' ) { - $errstring += ' You have used up '+$data['lockout_fails']+' out of '+$data['lockout_threshold']+' login attempts. After you have used up all '+$data['lockout_threshold']+' login attempts, you will have to enter a visual confirmation code before logging in, effective for '+$data['lockout_duration']+' minutes.'; + $errstring += $lang.get('user_err_invalid_credentials_lockout_captcha', subst); } break; case 'backend_fail': - $errstring = 'You entered the right credentials and everything was validated, but for some reason Enano couldn\'t register your session. This is an internal problem with the site and you are encouraged to contact site administration.'; + $errstring = $lang.get('user_err_backend_fail'); break; case 'locked_out': $attempts = parseInt($data['lockout_fails']); if ( $attempts > $data['lockout_threshold']) $attempts = $data['lockout_threshold']; $time_rem = $data.time_rem; - $s = ( $time_rem == 1 ) ? '' : 's'; - $errstring = "You have used up all "+$data['lockout_threshold']+" allowed login attempts. Please wait "+$time_rem+" minute"+$s+" before attempting to log in again"; - if ( $data['lockout_policy'] == 'captcha' ) - $errstring += ', or enter the visual confirmation code shown above in the appropriate box'; - $errstring += '.'; + $s = ( $time_rem == 1 ) ? '' : $lang.get('meta_plural'); + + var subst = { + lockout_threshold: $data.lockout_threshold, + time_rem: $time_rem, + plural: $s, + captcha_blurb: ( $data.lockout_policy == 'captcha' ? $lang.get('user_err_locked_out_captcha_blurb') : '' ) + } + + $errstring = $lang.get('user_err_locked_out', subst); + break; } return $errstring; @@ -358,11 +369,11 @@ level = USER_LEVEL_MEMBER; ajax_auth_level_cache = level; var loading_win = '
\ -

Fetching an encryption key...

\ -

Not working? Use the alternate login form.

\ +

' + $lang.get('user_login_ajax_fetching_key') + '

\ +

' + $lang.get('user_login_ajax_link_fullform', { link_full_form: makeUrlNS('Special', 'Login/' + title) }) + '

\

Please wait...

\
'; - var title = ( level > USER_LEVEL_MEMBER ) ? 'You are requesting a sensitive operation.' : 'Please enter your username and password to continue.'; + var title = ( level > USER_LEVEL_MEMBER ) ? $lang.get('user_login_ajax_prompt_title_elev') : $lang.get('user_login_ajax_prompt_title'); ajax_auth_mb_cache = new messagebox(MB_OKCANCEL|MB_ICONLOCK, title, loading_win); ajax_auth_mb_cache.onbeforeclick['OK'] = ajaxValidateLogin; ajax_auth_mb_cache.onbeforeclick['Cancel'] = function() @@ -416,13 +427,13 @@ } else if ( level > USER_LEVEL_MEMBER ) { - form_html += 'Please re-enter your login details, to verify your identity.

'; + form_html += $lang.get('user_login_ajax_prompt_body_elev') + '

'; } if ( ajax_auth_show_captcha ) { var captcha_html = ' \ \ - Code in image: \ + ' + $lang.get('user_login_field_captcha') + ': \ \ '; } @@ -434,22 +445,22 @@ form_html += ' \ \ \ - \ \ - \ ' + captcha_html + ' \ \ \ \
Username: \ + ' + $lang.get('user_login_field_username') + ': \
Password: \ + ' + $lang.get('user_login_field_password') + ': \
\ -
Trouble logging in? Try the full login form.
'; + ' + $lang.get('user_login_ajax_link_fullform', { link_full_form: makeUrlNS('Special', 'Login/' + title, 'level=' + level) }) + '
'; if ( level <= USER_LEVEL_MEMBER ) { form_html += ' \ - Did you forget your password?
\ - Maybe you need to create an account.
'; + ' + $lang.get('user_login_ajax_link_forgotpass', { forgotpass_link: makeUrlNS('Special', 'PasswordReset') }) + '
\ + ' + $lang.get('user_login_createaccount_blurb', { reg_link: makeUrlNS('Special', 'Register') }); } - form_html += ' \ + form_html += '
\
\ @@ -588,7 +599,7 @@ json_data = encodeURIComponent(json_data); var loading_win = '
\ -

Logging in...

\ +

' + $lang.get('user_login_ajax_loggingin') + '

\

Please wait...

\
'; diff -r 8a00247d1dee -r 2b283402e4e4 includes/functions.php --- a/includes/functions.php Sun Oct 28 14:32:13 2007 -0400 +++ b/includes/functions.php Sun Oct 28 16:40:24 2007 -0400 @@ -273,15 +273,19 @@ * @param string $timeout Timeout, in seconds, to delay the redirect. Defaults to 3. */ -function redirect($url, $title = 'Redirecting...', $message = 'Please wait while you are redirected.', $timeout = 3) +function redirect($url, $title = 'etc_redirect_title', $message = 'etc_redirect_body', $timeout = 3) { global $db, $session, $paths, $template, $plugins; // Common objects + global $lang; if ( $timeout == 0 ) { header('Location: ' . $url); header('HTTP/1.1 307 Temporary Redirect'); } + + $title = $lang->get($title); + $message = $lang->get($message); $template->add_header(''); $template->add_header('"); + // Generate the code for the Log out and Change theme sidebar buttons // Once again, the new template parsing system can be used here @@ -710,7 +715,8 @@ } } $js_dynamic .= '\'; - var ENANO_CURRENT_THEME = \''. $session->theme .'\';'; + var ENANO_CURRENT_THEME = \''. $session->theme .'\'; + var ENANO_LANG_ID = ' . $lang->lang_id . ';'; foreach($paths->nslist as $k => $c) { $js_dynamic .= "namespace_list['{$k}'] = '$c';"; diff -r 8a00247d1dee -r 2b283402e4e4 language/english/enano.json --- a/language/english/enano.json Sun Oct 28 14:32:13 2007 -0400 +++ b/language/english/enano.json Sun Oct 28 16:40:24 2007 -0400 @@ -17,7 +17,7 @@ var enano_lang = { categories: [ - 'meta', 'user', 'page', 'comment', 'onpage' + 'meta', 'user', 'page', 'comment', 'onpage', 'etc' ], strings: { meta: { @@ -31,15 +31,6 @@ user: { login_message_short: 'Please enter your username and password to log in.', login_message_short_elev: 'Please re-enter your login details', - err_key_not_found: 'Enano couldn\'t look up the encryption key used to encrypt your password. This most often happens if a cache rotation occurred during your login attempt, or if you refreshed the login page.', - err_key_wrong_length: 'The encryption key was the wrong length.', - err_too_big_for_britches: 'You are trying to authenticate at a level that your user account does not permit.', - err_invalid_credentials: 'You have entered an invalid username or password. Please enter your login details again.', - err_invalid_credentials_lockout: ' You have used up %fails% out of %config.lockout_threshold% login attempts. After you have used up all %config.lockout_threshold% login attempts, you will be locked out from logging in for %config.lockout_duration% minutes.', - err_invalid_credentials_lockout_captcha: ' You have used up %lockout_fails% out of %config.lockout_threshold% login attempts. After you have used up all %config.lockout_threshold% login attempts, you will have to enter a visual confirmation code while logging in, effective for %config.lockout_duration% minutes.', - err_backend_fail: 'You entered the right credentials and everything was validated, but for some reason Enano couldn\'t register your session. This is an internal problem with the site and you are encouraged to contact site administration.', - err_locked_out: 'You have used up all %config.lockout_threshold% allowed login attempts. Please wait %time_rem% minute%plural% before attempting to log in again%config.captcha_blurb%.', - err_locked_out_captcha_blurb: ', or enter the visual confirmation code shown above in the appropriate box', login_body: 'Logging in enables you to use your preferences and access member information. If you don\'t have a username and password here, you can create an account.', login_body_elev: 'You are requesting that a sensitive operation be performed. To continue, please re-enter your password to confirm your identity.', login_field_username: 'Username', @@ -47,9 +38,39 @@ login_forgotpass_blurb: 'Forgot your password? No problem.', login_createaccount_blurb: 'Maybe you need to create an account.', login_field_captcha: 'Code in image', - login_nocrypt_title: 'Important note regarding cryptography', + login_nocrypt_title: 'Important note regarding cryptography:', login_nocrypt_body: 'Some countries do not allow the import or use of cryptographic technology. If you live in one of the countries listed below, you should log in without using encryption.', login_nocrypt_countrylist: 'This restriction applies to the following countries: Belarus, China, India, Israel, Kazakhstan, Mongolia, Pakistan, Russia, Saudi Arabia, Singapore, Tunisia, Venezuela, and Vietnam.', + login_usecrypt_title: 'Encryption is currently turned off.', + login_usecrypt_body: 'If you are not in one of the countries listed below, you should enable encryption to secure the logon process.', + login_usecrypt_countrylist: 'The cryptography restriction applies to the following countries: Belarus, China, India, Israel, Kazakhstan, Mongolia, Pakistan, Russia, Saudi Arabia, Singapore, Tunisia, Venezuela, and Vietnam.', + login_success_title: 'Login successful', + login_success_body: 'You have successfully logged into the %config.site_name% site as "%username%". Redirecting to %redir_target%...', + + login_ajax_fetching_key: 'Fetching an encryption key...', + login_ajax_prompt_title: 'Please enter your username and password to continue.', + login_ajax_prompt_title_elev: 'You are requesting a sensitive operation.', + login_ajax_prompt_body_elev: 'Please re-enter your login details, to verify your identity.', + login_ajax_link_fullform: 'Trouble logging in? Try the full login form.', + login_ajax_link_forgotpass: 'Did you forget your password?', + login_ajax_loggingin: 'Logging in...', + + err_key_not_found: 'Enano couldn\'t look up the encryption key used to encrypt your password. This most often happens if a cache rotation occurred during your login attempt, or if you refreshed the login page.', + err_key_wrong_length: 'The encryption key was the wrong length.', + err_too_big_for_britches: 'You are trying to authenticate at a level that your user account does not permit.', + err_invalid_credentials: 'You have entered an invalid username or password. Please enter your login details again.', + err_invalid_credentials_lockout: ' You have used up %fails% out of %config.lockout_threshold% login attempts. After you have used up all %config.lockout_threshold% login attempts, you will be locked out from logging in for %config.lockout_duration% minutes.', + err_invalid_credentials_lockout_captcha: ' You have used up %lockout_fails% out of %config.lockout_threshold% login attempts. After you have used up all %config.lockout_threshold% login attempts, you will have to enter a visual confirmation code while logging in, effective for %config.lockout_duration% minutes.', + err_backend_fail: 'You entered the right credentials and everything was validated, but for some reason Enano couldn\'t register your session. This is an internal problem with the site and you are encouraged to contact site administration.', + err_locked_out: 'You have used up all %config.lockout_threshold% allowed login attempts. Please wait %time_rem% minute%plural% before attempting to log in again%captcha_blurb%.', + err_locked_out_captcha_blurb: ', or enter the visual confirmation code shown above in the appropriate box', + + logout_success_title: 'Logged out', + logout_success_body: 'You have been successfully logged out, and all cookies have been cleared. You will now be transferred to the main page.', + logout_confirm_title: 'Are you sure you want to log out?', + logout_confirm_body: 'If you log out, you will no longer be able to access your user preferences, your private messages, or certain areas of this site until you log in again.', + logout_confirm_title_elev: 'Are you sure you want to de-authenticate?', + logout_confirm_body_elev: 'If you de-authenticate, you will no longer be able to use the administration panel until you re-authenticate again. You may do so at any time using the Administration button on the sidebar.', }, page: { }, @@ -57,6 +78,11 @@ }, admhome: { }, + etc: { + redirect_title: 'Redirecting...', + redirect_body: 'Please wait while you are redirected.', + redirect_timeout: 'If you are not redirected within %timeout% seconds, please click here.', + }, } }; diff -r 8a00247d1dee -r 2b283402e4e4 plugins/SpecialAdmin.php --- a/plugins/SpecialAdmin.php Sun Oct 28 14:32:13 2007 -0400 +++ b/plugins/SpecialAdmin.php Sun Oct 28 16:40:24 2007 -0400 @@ -2620,7 +2620,7 @@ } if ( t == namespace_list.Admin + 'AdminLogout' ) { - var mb = new messagebox(MB_YESNO|MB_ICONQUESTION, 'Are you sure you want to de-authenticate?', 'If you de-authenticate, you will no longer be able to use the administration panel until you re-authenticate again. You may do so at any time using the Administration button on the sidebar.'); + var mb = new messagebox(MB_YESNO|MB_ICONQUESTION, $lang.get('user_logout_confirm_title_elev'), $lang.get('user_logout_confirm_body_elev')); mb.onclick['Yes'] = function() { var tigraentry = document.getElementById('i_div0_0').parentNode; var tigraobj = $(tigraentry); diff -r 8a00247d1dee -r 2b283402e4e4 plugins/SpecialUserFuncs.php --- a/plugins/SpecialUserFuncs.php Sun Oct 28 14:32:13 2007 -0400 +++ b/plugins/SpecialUserFuncs.php Sun Oct 28 16:40:24 2007 -0400 @@ -90,6 +90,14 @@ \'namespace\'=>\'Special\', \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\', )); + + $paths->add_page(Array( + \'name\'=>\'Language exporter\', + \'urlname\'=>\'LangExportJSON\', + \'namespace\'=>\'Special\', + \'special\'=>0,\'visible\'=>0,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\', + )); + '); // function names are IMPORTANT!!! The name pattern is: page__ @@ -305,18 +313,26 @@ - getAllParams() ) ? '/' . $return : ''; - $nocrypt_link = makeUrlNS('Special', "Login$returnpage_link", "level=$level&use_crypt=0", true); - echo '

' . $lang->get('user_login_nocrypt_title') . ': ' . $lang->get('user_login_nocrypt_body', array('nocrypt_link' => $nocrypt_link)) . '

'; - echo '

' . $lang->get('user_login_nocrypt_countrylist') . '

'; + if ( $level <= USER_LEVEL_MEMBER && ( !isset($_GET['use_crypt']) || ( isset($_GET['use_crypt']) && $_GET['use_crypt']!='0' ) ) ) + { + $returnpage_link = ( $return = $paths->getAllParams() ) ? '/' . $return : ''; + $nocrypt_link = makeUrlNS('Special', "Login$returnpage_link", "level=$level&use_crypt=0", true); + echo '

' . $lang->get('user_login_nocrypt_title') . ' ' . $lang->get('user_login_nocrypt_body', array('nocrypt_link' => $nocrypt_link)) . '

'; + echo '

' . $lang->get('user_login_nocrypt_countrylist') . '

'; + } + else if ( $level <= USER_LEVEL_MEMBER && ( isset($_GET['use_crypt']) && $_GET['use_crypt']=='0' ) ) + { + $returnpage_link = ( $return = $paths->getAllParams() ) ? '/' . $return : ''; + $usecrypt_link = makeUrlNS('Special', "Login$returnpage_link", "level=$level&use_crypt=1", true); + echo '

' . $lang->get('user_login_usecrypt_title') . ' ' . $lang->get('user_login_usecrypt_body', array('usecrypt_link' => $usecrypt_link)) . '

'; + echo '

' . $lang->get('user_login_usecrypt_countrylist') . '

'; + } ?> - @@ -348,6 +364,7 @@ { global $db, $session, $paths, $template, $plugins; // Common objects global $__login_status; + global $lang; if ( isset($_GET['act']) && $_GET['act'] == 'ajaxlogin' ) { $plugins->attachHook('login_password_reset', 'SpecialLogin_SendResponse_PasswordReset($row[\'user_id\'], $row[\'temp_password\']);'); @@ -403,7 +420,11 @@ if(isset($_POST['return_to'])) { $name = ( isset($paths->pages[$_POST['return_to']]['name']) ) ? $paths->pages[$_POST['return_to']]['name'] : $_POST['return_to']; - redirect( makeUrl($_POST['return_to'], false, true), 'Login successful', 'You have successfully logged into the '.getConfig('site_name').' site as "'.$session->username.'". Redirecting to ' . $name . '...' ); + $subst = array( + 'username' => $session->username, + 'redir_target' => $name + ); + redirect( makeUrl($_POST['return_to'], false, true), $lang->get('user_login_success_title'), $lang->get('user_login_success_body', $subst) ); } else { @@ -437,13 +458,15 @@ function page_Special_Logout() { global $db, $session, $paths, $template, $plugins; // Common objects + global $lang; if ( !$session->user_logged_in ) $paths->main_page(); $l = $session->logout(); if ( $l == 'success' ) { - redirect(makeUrl(getConfig('main_page'), false, true), 'Logged out', 'You have been successfully logged out, and all cookies have been cleared. You will now be transferred to the main page.', 4); + + redirect(makeUrl(getConfig('main_page'), false, true), $lang->get('user_logout_success_title'), $lang->get('user_logout_success_body'), 4); } $template->header(); echo '

An error occurred during the logout process.

'.$l.'

'; @@ -1631,4 +1654,31 @@ } } +function page_Special_LangExportJSON() +{ + global $db, $session, $paths, $template, $plugins; // Common objects + global $lang; + + $lang_id = ( $x = $paths->getParam(0) ) ? intval($x) : $lang->lang_id; + + if ( $lang->lang_id == $lang_id ) + $lang_local =& $lang; + else + $lang_local = new Language($lang_id); + + $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); + + $timestamp = date('D, j M Y H:i:s T', $lang_local->lang_timestamp); + header("Last-Modified: $timestamp"); + header("Date: $timestamp"); + header('Content-type: text/javascript'); + + $lang_local->fetch(); + echo "if ( typeof(enano_lang) != 'object' ) + var enano_lang = new Object(); + +enano_lang[{$lang->lang_id}] = " . $json->encode($lang_local->strings) . ";"; + +} + ?> \ No newline at end of file diff -r 8a00247d1dee -r 2b283402e4e4 schema.sql --- a/schema.sql Sun Oct 28 14:32:13 2007 -0400 +++ b/schema.sql Sun Oct 28 16:40:24 2007 -0400 @@ -272,6 +272,7 @@ lang_code varchar(16) NOT NULL, lang_name_default varchar(64) NOT NULL, lang_name_native varchar(64) NOT NULL, + last_changed int(12) NOT NULL DEFAULT 0, PRIMARY KEY ( lang_id ) ) CHARACTER SET `utf8`; diff -r 8a00247d1dee -r 2b283402e4e4 upgrade.sql --- a/upgrade.sql Sun Oct 28 14:32:13 2007 -0400 +++ b/upgrade.sql Sun Oct 28 16:40:24 2007 -0400 @@ -9,7 +9,7 @@ -- UPDATE {{TABLE_PREFIX}}group_members SET group_id=9998 WHERE group_id=4; -- INSERT INTO {{TABLE_PREFIX}}groups(group_id,group_name,group_type,system_group) VALUES(4, 'Regular members', 3, 1); CREATE TABLE {{TABLE_PREFIX}}lockout( id int(12) NOT NULL auto_increment, ipaddr varchar(40) NOT NULL, action ENUM('credential', 'level') NOT NULL DEFAULT 'credential', timestamp int(12) NOT NULL DEFAULT 0, PRIMARY KEY ( id ) ) CHARACTER SET `utf8`; -CREATE TABLE {{TABLE_PREFIX}}language( lang_id smallint(5) NOT NULL auto_increment, lang_code varchar(16) NOT NULL, lang_name_default varchar(64) NOT NULL, lang_name_native varchar(64) NOT NULL, PRIMARY KEY ( lang_id ) ) CHARACTER SET `utf8`; +CREATE TABLE {{TABLE_PREFIX}}language( lang_id smallint(5) NOT NULL auto_increment, lang_code varchar(16) NOT NULL, lang_name_default varchar(64) NOT NULL, lang_name_native varchar(64) NOT NULL, last_changed int(12) NOT NULL DEFAULT 0, PRIMARY KEY ( lang_id ) ) CHARACTER SET `utf8`; CREATE TABLE {{TABLE_PREFIX}}language_strings( string_id bigint(15) NOT NULL auto_increment, lang_id smallint(5) NOT NULL, string_category varchar(32) NOT NULL, string_name varchar(64) NOT NULL, string_content longtext NOT NULL, PRIMARY KEY ( string_id ) ); ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_lang smallint(5) NOT NULL; ---END Stable1.0ToUnstable1.1---