mirror of
https://github.com/kovidgoyal/calibre.git
synced 2026-05-30 18:45:20 -04:00
Merge from trunk
This commit is contained in:
@@ -1465,7 +1465,7 @@ class StoreVirtualoStore(StoreBase):
|
||||
actual_plugin = 'calibre.gui2.store.stores.virtualo_plugin:VirtualoStore'
|
||||
|
||||
headquarters = 'PL'
|
||||
formats = ['EPUB', 'PDF']
|
||||
formats = ['EPUB', 'MOBI', 'PDF']
|
||||
|
||||
class StoreWaterstonesUKStore(StoreBase):
|
||||
name = 'Waterstones UK'
|
||||
|
||||
@@ -36,7 +36,8 @@ class ANDROID(USBMS):
|
||||
0xca2 : [0x100, 0x0227, 0x0226, 0x222],
|
||||
0xca3 : [0x100, 0x0227, 0x0226, 0x222],
|
||||
0xca4 : [0x100, 0x0227, 0x0226, 0x222],
|
||||
0xca9 : [0x100, 0x0227, 0x0226, 0x222]
|
||||
0xca9 : [0x100, 0x0227, 0x0226, 0x222],
|
||||
0xcac : [0x100, 0x0227, 0x0226, 0x222],
|
||||
},
|
||||
|
||||
# Eken
|
||||
@@ -138,8 +139,12 @@ class ANDROID(USBMS):
|
||||
# Advent
|
||||
0x0955 : { 0x7100 : [0x9999] }, # This is the same as the Notion Ink Adam
|
||||
|
||||
# Kobo
|
||||
0x2237: { 0x2208 : [0x0226] },
|
||||
|
||||
}
|
||||
EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books']
|
||||
EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books',
|
||||
'sdcard/ebooks']
|
||||
EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of directories to '
|
||||
'send e-books to on the device. The first one that exists will '
|
||||
'be used')
|
||||
@@ -149,7 +154,7 @@ class ANDROID(USBMS):
|
||||
'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX', 'GOOGLE', 'ARCHOS',
|
||||
'TELECHIP', 'HUAWEI', 'T-MOBILE', 'SEMC', 'LGE', 'NVIDIA',
|
||||
'GENERIC-', 'ZTE', 'MID', 'QUALCOMM', 'PANDIGIT', 'HYSTON',
|
||||
'VIZIO', 'GOOGLE', 'FREESCAL']
|
||||
'VIZIO', 'GOOGLE', 'FREESCAL', 'KOBO_INC']
|
||||
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
|
||||
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
|
||||
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID',
|
||||
@@ -160,7 +165,8 @@ class ANDROID(USBMS):
|
||||
'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE', 'A7EB', 'STREAK',
|
||||
'MB525', 'ANDROID2.3', 'SGH-I997', 'GT-I5800_CARD', 'MB612',
|
||||
'GT-S5830_CARD', 'GT-S5570_CARD', 'MB870', 'MID7015A',
|
||||
'ALPANDIGITAL', 'ANDROID_MID', 'VTAB1008', 'EMX51_BBG_ANDROI']
|
||||
'ALPANDIGITAL', 'ANDROID_MID', 'VTAB1008', 'EMX51_BBG_ANDROI',
|
||||
'UMS', '.K080']
|
||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
|
||||
|
||||
@@ -61,18 +61,25 @@ class KOBO(USBMS):
|
||||
' ebook file itself. With this option, calibre will send a '
|
||||
'separate cover image to the reader, useful if you '
|
||||
'have modified the cover.'),
|
||||
_('Upload Black and White Covers')
|
||||
_('Upload Black and White Covers'),
|
||||
_('Show expired books') +
|
||||
':::'+_('A bug in an earlier version left non kepubs book records'
|
||||
' in the datbase. With this option Calibre will show the '
|
||||
'expired records and allow you to delete them with '
|
||||
'the new delete logic.'),
|
||||
]
|
||||
|
||||
EXTRA_CUSTOMIZATION_DEFAULT = [
|
||||
', '.join(['tags']),
|
||||
True,
|
||||
True,
|
||||
True
|
||||
]
|
||||
|
||||
OPT_COLLECTIONS = 0
|
||||
OPT_UPLOAD_COVERS = 1
|
||||
OPT_UPLOAD_GRAYSCALE_COVERS = 2
|
||||
OPT_SHOW_EXPIRED_BOOK_RECORDS = 3
|
||||
|
||||
def initialize(self):
|
||||
USBMS.initialize(self)
|
||||
@@ -232,18 +239,23 @@ class KOBO(USBMS):
|
||||
self.dbversion = result[0]
|
||||
|
||||
debug_print("Database Version: ", self.dbversion)
|
||||
|
||||
opts = self.settings()
|
||||
if self.dbversion >= 16:
|
||||
query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
|
||||
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
|
||||
'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility from content where ' \
|
||||
'BookID is Null and ( ___ExpirationStatus <> "3" or ___ExpirationStatus is Null)'
|
||||
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \
|
||||
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')
|
||||
elif self.dbversion < 16 and self.dbversion >= 14:
|
||||
query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
|
||||
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
|
||||
'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, "-1" as Accessibility from content where ' \
|
||||
'BookID is Null and ( ___ExpirationStatus <> "3" or ___ExpirationStatus is Null)'
|
||||
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \
|
||||
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')
|
||||
elif self.dbversion < 14 and self.dbversion >= 8:
|
||||
query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
|
||||
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
|
||||
'ImageID, ReadStatus, ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility from content where ' \
|
||||
'BookID is Null and ( ___ExpirationStatus <> "3" or ___ExpirationStatus is Null)'
|
||||
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \
|
||||
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')
|
||||
else:
|
||||
query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
|
||||
'ImageID, ReadStatus, "-1" as ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility from content where BookID is Null'
|
||||
@@ -343,21 +355,23 @@ class KOBO(USBMS):
|
||||
# Kobo does not delete the Book row (ie the row where the BookID is Null)
|
||||
# The next server sync should remove the row
|
||||
cursor.execute('delete from content where BookID = ?', t)
|
||||
try:
|
||||
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0, ___ExpirationStatus=3 ' \
|
||||
'where BookID is Null and ContentID =?',t)
|
||||
except Exception as e:
|
||||
if 'no such column' not in str(e):
|
||||
raise
|
||||
if ContentType == 6:
|
||||
try:
|
||||
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0 ' \
|
||||
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0, ___ExpirationStatus=3 ' \
|
||||
'where BookID is Null and ContentID =?',t)
|
||||
except Exception as e:
|
||||
if 'no such column' not in str(e):
|
||||
raise
|
||||
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\' ' \
|
||||
'where BookID is Null and ContentID =?',t)
|
||||
|
||||
try:
|
||||
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0 ' \
|
||||
'where BookID is Null and ContentID =?',t)
|
||||
except Exception as e:
|
||||
if 'no such column' not in str(e):
|
||||
raise
|
||||
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\' ' \
|
||||
'where BookID is Null and ContentID =?',t)
|
||||
else:
|
||||
cursor.execute('delete from content where BookID is Null and ContentID =?',t)
|
||||
|
||||
connection.commit()
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
@@ -9,6 +10,8 @@
|
||||
#include <fcntl.h>
|
||||
|
||||
#define MARKER ".created_by_calibre_mount_helper"
|
||||
#define DEV "/dev/"
|
||||
#define MEDIA "/media/"
|
||||
#define False 0
|
||||
#define True 1
|
||||
|
||||
@@ -33,6 +36,43 @@ void ensure_root() {
|
||||
}
|
||||
}
|
||||
|
||||
int check_args(const char *dev, const char *mp) {
|
||||
char buffer[PATH_MAX+1];
|
||||
|
||||
if (dev == NULL || strlen(dev) < strlen(DEV) || mp == NULL || strlen(mp) < strlen(MEDIA)) {
|
||||
fprintf(stderr, "Invalid arguments\n");
|
||||
return False;
|
||||
}
|
||||
|
||||
if (exists(mp)) {
|
||||
if (realpath(mp, buffer) == NULL) {
|
||||
fprintf(stderr, "Unable to resolve mount path\n");
|
||||
return False;
|
||||
}
|
||||
if (strncmp(MEDIA, buffer, strlen(MEDIA)) != 0) {
|
||||
fprintf(stderr, "Trying to operate on a mount point not under /media is not allowed\n");
|
||||
return False;
|
||||
}
|
||||
}
|
||||
|
||||
if (strncmp(MEDIA, mp, strlen(MEDIA)) != 0) {
|
||||
fprintf(stderr, "Trying to operate on a mount point not under /media is not allowed\n");
|
||||
return False;
|
||||
}
|
||||
|
||||
if (realpath(dev, buffer) == NULL) {
|
||||
fprintf(stderr, "Unable to resolve dev path\n");
|
||||
return False;
|
||||
}
|
||||
|
||||
if (strncmp(DEV, buffer, strlen(DEV)) != 0) {
|
||||
fprintf(stderr, "Trying to operate on a dev node not under /dev\n");
|
||||
return False;
|
||||
}
|
||||
|
||||
return True;
|
||||
}
|
||||
|
||||
int do_mount(const char *dev, const char *mp) {
|
||||
char options[1000], marker[2000];
|
||||
#ifdef __NetBSD__
|
||||
@@ -44,6 +84,7 @@ int do_mount(const char *dev, const char *mp) {
|
||||
fprintf(stderr, "Specified device node does not exist\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!exists(mp)) {
|
||||
if (mkdir(mp, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) != 0) {
|
||||
errsv = errno;
|
||||
@@ -211,6 +252,17 @@ int main(int argc, char** argv)
|
||||
}
|
||||
action = argv[1]; dev = argv[2]; mp = argv[3];
|
||||
|
||||
/* Ensure that PATH only contains system directories to prevent execution of
|
||||
arbitrary executables as root */
|
||||
if (setenv("PATH",
|
||||
"/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin\0",
|
||||
1) != 0) {
|
||||
fprintf(stderr, "Failed to restrict PATH env var, aborting.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (!check_args(dev, mp)) exit(EXIT_FAILURE);
|
||||
|
||||
if (strncmp(action, "mount", 5) == 0) {
|
||||
status = do_mount(dev, mp);
|
||||
} else if (strncmp(action, "eject", 5) == 0) {
|
||||
|
||||
@@ -560,14 +560,21 @@ class PRST1(USBMS):
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
periodical_schema = \
|
||||
"'http://xmlns.sony.net/e-book/prs/periodicals/1.0/newspaper/1.0'"
|
||||
# Setting this to the SONY periodical schema apparently causes errors
|
||||
# with some periodicals, therefore set it to null, since the special
|
||||
# periodical navigation doesn't work anyway.
|
||||
periodical_schema = 'null'
|
||||
|
||||
query = '''
|
||||
UPDATE books
|
||||
SET conforms_to = 'http://xmlns.sony.net/e-book/prs/periodicals/1.0/newspaper/1.0',
|
||||
SET conforms_to = %s,
|
||||
periodical_name = ?,
|
||||
description = ?,
|
||||
publication_date = ?
|
||||
WHERE _id = ?
|
||||
'''
|
||||
'''%periodical_schema
|
||||
t = (name, None, pubdate, book.bookId,)
|
||||
cursor.execute(query, t)
|
||||
|
||||
|
||||
@@ -109,14 +109,16 @@ class HTMLFile(object):
|
||||
|
||||
try:
|
||||
with open(self.path, 'rb') as f:
|
||||
src = f.read()
|
||||
src = f.read(4096)
|
||||
self.is_binary = level > 0 and not bool(self.HTML_PAT.search(src))
|
||||
if not self.is_binary:
|
||||
src += f.read()
|
||||
except IOError as err:
|
||||
msg = 'Could not read from file: %s with error: %s'%(self.path, as_unicode(err))
|
||||
if level == 0:
|
||||
raise IOError(msg)
|
||||
raise IgnoreFile(msg, err.errno)
|
||||
|
||||
self.is_binary = level > 0 and not bool(self.HTML_PAT.search(src[:4096]))
|
||||
if not self.is_binary:
|
||||
if not encoding:
|
||||
encoding = xml_to_unicode(src[:4096], verbose=verbose)[-1]
|
||||
|
||||
@@ -15,7 +15,6 @@ from calibre.customize.conversion import OutputFormatPlugin, \
|
||||
OptionRecommendation
|
||||
from calibre.ebooks.metadata.opf2 import OPF
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.ebooks.pdf.writer import PDFWriter, ImagePDFWriter, PDFMetadata
|
||||
from calibre.ebooks.pdf.pageoptions import UNITS, PAPER_SIZES, \
|
||||
ORIENTATIONS
|
||||
|
||||
@@ -90,6 +89,7 @@ class PDFOutput(OutputFormatPlugin):
|
||||
self.convert_text(oeb_book)
|
||||
|
||||
def convert_images(self, images):
|
||||
from calibre.ebooks.pdf.writer import ImagePDFWriter
|
||||
self.write(ImagePDFWriter, images)
|
||||
|
||||
def get_cover_data(self):
|
||||
@@ -105,6 +105,7 @@ class PDFOutput(OutputFormatPlugin):
|
||||
self.cover_data = None
|
||||
|
||||
def convert_text(self, oeb_book):
|
||||
from calibre.ebooks.pdf.writer import PDFWriter
|
||||
self.log.debug('Serializing oeb input to disk for processing...')
|
||||
self.get_cover_data()
|
||||
|
||||
@@ -119,6 +120,7 @@ class PDFOutput(OutputFormatPlugin):
|
||||
self.write(PDFWriter, [s.path for s in opf.spine])
|
||||
|
||||
def write(self, Writer, items):
|
||||
from calibre.ebooks.pdf.writer import PDFMetadata
|
||||
writer = Writer(self.opts, self.log, cover_data=self.cover_data)
|
||||
|
||||
close = False
|
||||
|
||||
@@ -587,7 +587,6 @@ class CoversModel(QAbstractListModel): # {{{
|
||||
return 1
|
||||
return pmap.width()*pmap.height()
|
||||
|
||||
|
||||
def clear_failed(self):
|
||||
good = []
|
||||
pmap = {}
|
||||
@@ -729,7 +728,8 @@ class CoversWidget(QWidget): # {{{
|
||||
except Empty:
|
||||
break
|
||||
|
||||
self.covers_view.clear_failed()
|
||||
if self.continue_processing:
|
||||
self.covers_view.clear_failed()
|
||||
|
||||
if self.worker.error is not None:
|
||||
error_dialog(self, _('Download failed'),
|
||||
@@ -759,7 +759,7 @@ class CoversWidget(QWidget): # {{{
|
||||
self.continue_processing = False
|
||||
|
||||
def cancel(self):
|
||||
self.continue_processing = False
|
||||
self.cleanup()
|
||||
self.abort.set()
|
||||
|
||||
def cover_pixmap(self):
|
||||
|
||||
@@ -34,10 +34,11 @@ class VirtualoStore(BasicStoreConfig, StorePlugin):
|
||||
d.set_tags(self.config.get('tags', ''))
|
||||
d.exec_()
|
||||
|
||||
def search(self, query, max_results=10, timeout=60):
|
||||
url = 'http://virtualo.pl/c2/?q=' + urllib.quote(query)
|
||||
def search(self, query, max_results=12, timeout=60):
|
||||
url = 'http://virtualo.pl/?q=' + urllib.quote(query) + '&f=format_id:4,6,3'
|
||||
|
||||
br = browser()
|
||||
drm_pattern = re.compile("ADE")
|
||||
|
||||
counter = max_results
|
||||
with closing(br.open(url, timeout=timeout)) as f:
|
||||
@@ -46,26 +47,28 @@ class VirtualoStore(BasicStoreConfig, StorePlugin):
|
||||
if counter <= 0:
|
||||
break
|
||||
|
||||
id = ''.join(data.xpath('.//table/tr[2]/td[1]/a/@href'))
|
||||
id = ''.join(data.xpath('.//table/tr[1]/td[1]/a/@href'))
|
||||
if not id:
|
||||
continue
|
||||
|
||||
price = ''.join(data.xpath('.//span[@class="price"]/text() | .//span[@class="price abbr"]/text()'))
|
||||
cover_url = ''.join(data.xpath('.//table/tr[2]/td[1]/a/img/@src'))
|
||||
cover_url = ''.join(data.xpath('.//table/tr[1]/td[1]/a/img/@src'))
|
||||
title = ''.join(data.xpath('.//div[@class="title"]/a/text()'))
|
||||
author = ', '.join(data.xpath('.//div[@class="authors"]/a/text()'))
|
||||
formats = ', '.join(data.xpath('.//span[@class="format"]/a/text()'))
|
||||
formats = re.sub(r'(, )?ONLINE(, )?', '', formats)
|
||||
drm = drm_pattern.search(formats)
|
||||
formats = re.sub(r'(, )?ADE(, )?', '', formats)
|
||||
|
||||
counter -= 1
|
||||
|
||||
s = SearchResult()
|
||||
s.cover_url = cover_url
|
||||
s.cover_url = cover_url.split('.jpg')[0] + '.jpg'
|
||||
s.title = title.strip() + ' ' + formats
|
||||
s.author = author.strip()
|
||||
s.price = price + ' zł'
|
||||
s.detail_item = 'http://virtualo.pl' + id.strip()
|
||||
s.detail_item = 'http://virtualo.pl' + id.strip().split('http://')[0]
|
||||
s.formats = formats.upper().strip()
|
||||
s.drm = SearchResult.DRM_UNKNOWN
|
||||
s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED
|
||||
|
||||
yield s
|
||||
|
||||
@@ -156,7 +156,7 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS,
|
||||
body.append(HR())
|
||||
body.append(DIV(
|
||||
A(_('Switch to the full interface (non-mobile interface)'),
|
||||
href="/browse",
|
||||
href=prefix+"/browse",
|
||||
style="text-decoration: none; color: blue",
|
||||
title=_('The full interface gives you many more features, '
|
||||
'but it may not work well on a small screen')),
|
||||
|
||||
@@ -259,7 +259,7 @@ The following functions are available in addition to those described in single-f
|
||||
yy : the year as two digit number (00 to 99).
|
||||
yyyy : the year as four digit number.
|
||||
h : the hours without a leading 0 (0 to 11 or 0 to 23, depending on am/pm)
|
||||
hh : the minutes with a leading 0 (00 to 11 or 00 to 23, depending on am/pm)
|
||||
hh : the hours with a leading 0 (00 to 11 or 00 to 23, depending on am/pm)
|
||||
m : the minutes without a leading 0 (0 to 59)
|
||||
mm : the minutes with a leading 0 (00 to 59)
|
||||
s : the seconds without a leading 0 (0 to 59)
|
||||
|
||||
@@ -194,4 +194,8 @@ class SpooledTemporaryFile(tempfile.SpooledTemporaryFile):
|
||||
tempfile.SpooledTemporaryFile.__init__(self, max_size=max_size, suffix=suffix,
|
||||
prefix=prefix, dir=dir, mode=mode, bufsize=bufsize)
|
||||
|
||||
def better_mktemp(*args, **kwargs):
|
||||
fd, path = tempfile.mkstemp(*args, **kwargs)
|
||||
os.close(fd)
|
||||
return path
|
||||
|
||||
|
||||
@@ -235,7 +235,9 @@ def format_date(dt, format, assume_utc=False, as_utc=False):
|
||||
return ''
|
||||
return function_index[s[0]](s)
|
||||
|
||||
return re.sub('(s{1,2})|(m{1,2})|(h{1,2})|(ap)|(AP)|(d{1,4}|M{1,4}|(?:yyyy|yy))', repl_func, format)
|
||||
return re.sub(
|
||||
'(s{1,2})|(m{1,2})|(h{1,2})|(ap)|(AP)|(d{1,4}|M{1,4}|(?:yyyy|yy))',
|
||||
repl_func, format)
|
||||
|
||||
def replace_months(datestr, clang):
|
||||
# Replace months by english equivalent for parse_date
|
||||
|
||||
@@ -748,7 +748,7 @@ class BuiltinFormatDate(BuiltinFormatterFunction):
|
||||
'yy : the year as two digit number (00 to 99). '
|
||||
'yyyy : the year as four digit number. '
|
||||
'h : the hours without a leading 0 (0 to 11 or 0 to 23, depending on am/pm) '
|
||||
'hh : the minutes with a leading 0 (00 to 11 or 00 to 23, depending on am/pm) '
|
||||
'hh : the hours with a leading 0 (00 to 11 or 00 to 23, depending on am/pm) '
|
||||
'm : the minutes without a leading 0 (0 to 59) '
|
||||
'mm : the minutes with a leading 0 (00 to 59) '
|
||||
's : the seconds without a leading 0 (0 to 59) '
|
||||
|
||||
@@ -121,7 +121,7 @@ _extra_lang_codes = {
|
||||
'en_TH' : _('English (Thailand)'),
|
||||
'en_TR' : _('English (Turkey)'),
|
||||
'en_CY' : _('English (Cyprus)'),
|
||||
'en_CZ' : _('English (Czechoslovakia)'),
|
||||
'en_CZ' : _('English (Czech Republic)'),
|
||||
'en_PK' : _('English (Pakistan)'),
|
||||
'en_HR' : _('English (Croatia)'),
|
||||
'en_ID' : _('English (Indonesia)'),
|
||||
|
||||
Reference in New Issue
Block a user